LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

P-luminary

MyBatis实现OA系统项目实战

2023/11/7

慕课网办公OA平台

课程介绍
  • 需求说明与环境准备
  • 开发基于RBAC的访问控制模块
  • 开发多级请假审批流程

办公自动化OA系统

  • 办公自动化系统(Office Automation)是替代传统办公的解决方案
  • OA系统是利用软件技术构建的单位内部办公平台,用于辅助办公
  • 利用OA系统可将办公数据数字化,可扩大提高办公流程执行效率

项目需求

  • 慕课网办公OA系统要求采用多用户B/S架构设计开发
  • HR为每一位员工分配系统账户,员工用此账户登录系统
  • 公司采用分级定岗,从1-8依次提升,不同岗位薪资水平不同
    • 6级(含)以下员工为业务岗,对应人员执行公司业务事宜
    • 7-8级为管理岗,其中7级为部门经理8级为总经理
    • 业务岗与管理岗员工可用系统功能不同,要求允许灵活配置

请假流程

  • 公司所有员工都可以使用”请假申请”功能申请休假
  • 请假时间少于72小时,部门经理审批后直接通过
  • 请假时间大于72小时,部门经理审批后还需总经理进行审批
  • 部门经理只允许批准本部门员工申请
  • 部门经理请假需直接由总经理审批
  • 总经理提起请假申请,系统自动批准通过

搭建基础架构

框架&组件
  • MySQL 8
  • Mybatis 3.5
  • Alibaba Druid
  • Servlet 3.1
  • Freemarker 2.3
  • LayUl 2.5
工程结构
imooc-oa  eclipse工程项目
 /src - java源代码目录
 /WebContent - Web资源目录
 /css - css文件目录
 /js - js文件目录
 /image - 图片资源目录
 /upload - 上传文件目录
 /WEB-INF   //jsp数据来自controller 不允许在web中直接访问 要从控制器跳转
   /jsp - jsp页面目录
   /lib - jar文件目录
   /classes - 编译后的class目录
   /web.xml web描述符文件
包结构
com.imooc-oa //逆命名法
    /controller - 存放Servlet控制器类 //承上启下接收参数 调用逻辑 返回处理结果
    /service - 存放处理逻辑类model[伪数据库] //完成业务逻辑 service与dao进行传递调用
    /dao - Data Access Object 数据访问对象类 数据读写的java类 数据来自xml文件
    /entity - 存放实体类 JavaBean java中的简单对象
    /utils - 通用工具类 底层通用的工具类或方法

环境配置

Mybatis复习_resources.getresourceasreader的读取路径-CSDN博客

配置pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>imooc-oa</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>

    <dependencies>
        <!--Mybatis 框架-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--MySQL 8 JDBC驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <!--Druid数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>
        <!--Junit4单元测试框架-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <!--只参与Maven Test,不进行发布-->
            <scope>test</scope>
        </dependency>
        <!--Logback日志输出组件-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--Freemarker依赖-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.29</version>
        </dependency>
        <!--servlet-api-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--依赖只参与编译测试,不进行发布-->
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
<!--用Maven时必打的代码-->
                <!--利用Maven编译插件将编译级别提高至1.8,解决lambda表达式错误-->
                <groupId>org.apache.maven.plugins</groupId>
                <!--maven-compiler-plugin是Maven自带的编译插件-->
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <!--检查源码采用1.8规则,默认为1.5-->
                    <source>1.8</source>
                    <!--按1.8规则生成字节码-->
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>
配置数据库连接池
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <environments default="dev">
        <!--开发环境配置-->
        <environment id="dev">
            <!--事务管理器采用JDBC方式-->
            <transactionManager type="JDBC"></transactionManager>
            <!--利用Mybatis自带连接池管理连接-->
            <dataSource type="POOLED">
            <!--MyBatis与Druid的整合-->
                <!--JDBC连接属性-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/imooc-oa?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/test.xml"/>
    </mappers>
</configuration>

开发Mybatis

Mybatis复习_resources.getresourceasreader的读取路径-CSDN博客

test.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
    <select id="sample" resultType="string">
        select 'success'
    </select>
</mapper>
util-MybatisUtils.java
package util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;
import java.util.function.Function;

public class MybatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    //利用静态块在初始化类时实例化sqlSessionFactory
    static{
        Reader reader = null;
        try{
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        }catch(IOException e){
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 执行SELECT查询SQL
     * @param func 要执行查询语句的代码块
     * @return 查询结果
     */
    //用于数据的查询[极大的简化查询] mybatis执行SQL时一定要有mapper的xml
    public static Object executeQuery(Function<SqlSession,Object> func){ //函数式接口
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try{//具体查询交给Function实现 查询前完成连接的打开和关闭
            Object obj = func.apply(sqlSession);
            return obj;
        }finally {
            sqlSession.close(); //最后一步释放连接资源
        }
    }

    /**
     * 执行INSERT/UPDATE/DELETE写操作SQL
     * @param func 要执行的写操作代码块
     * @return 写操作后返回的结果
     */
    public static Object executeUpdate(Function<SqlSession,Object> func){ //函数式接口
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        try{//具体查询交给Function实现 查询前完成连接的打开和关闭
            Object obj = func.apply(sqlSession);
            sqlSession.commit();
            return obj;
        }catch (RuntimeException e){
            sqlSession.rollback();
            throw e;
        }finally {
            sqlSession.close(); //最后一步释放连接资源
        }
    }
}
MybatisUtilsTestor.java
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import util.MybatisUtils;

public class MybatisUtilsTestor {
//    @Test
//    public void testcase1(){
//        String result = (String)MybatisUtils.executeQuery(sqlSession -> {
//            String out = (String)sqlSession.selectOne("test.sample");
//            return out; //out会被retrun obj接收 返回Object
//        });
//        System.out.println(result);
//    }
    @Test
    public void testcase2(){
        String result = (String) MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("test.sample"));
        System.out.println(result);
    }
}

MyBatis整合Druid连接池 (自定义连接池)

重新整合mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <environments default="dev">
        <!--开发环境配置-->
        <environment id="dev">
            <!--事务管理器采用JDBC方式-->
            <transactionManager type="JDBC"></transactionManager>
            <!--利用Mybatis自带连接池管理连接-->
<!--            <dataSource type="POOLED">-->
            <dataSource type="com.imooc.oa.datasource.DruidDataSourceFactory">
            <!--MyBatis与Druid的整合-->
                <!--JDBC连接属性-->
                <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/imooc-oa?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
                <!--连接池初始连接数-->
                <property name="initialSize" value="10"/>
                <!--连接池最大连接数-->
                <property name="maxActive" value="20"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/test.xml"/>
    </mappers>
</configuration>
com.imooc.oa.datasource.DruidDataSourceFactory
package com.imooc.oa.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

import javax.sql.DataSource;
import java.sql.SQLException;

public class DruidDataSourceFactory extends UnpooledDataSourceFactory {
    public DruidDataSourceFactory(){ //1.创造空的数据源对象
        // 2.调用setProperties读取xml对dataSource属性源进行设置
        this.dataSource = new DruidDataSource(); //表达数据源信息
    }
    //3.数据源需要额外设置要重写
    @Override
    public DataSource getDataSource() { //获取已经初始化的连接池进行返回
        try {
            ((DruidDataSource)this.dataSource).init(); //初始化Druid数据源
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return this.dataSource;
    }
}
Ctrl + Shift + N 文件查找对话框

整合Freemarker

pom.xml
    <!--Freemarker依赖-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.29</version>
        </dependency>
        <!--servlet-api-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--依赖只参与编译测试,不进行发布-->
            <scope>provided</scope>
        </dependency>
web-WEB-INF-ftl-test.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>${result}</h1>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>freemaker</servlet-name>
        <servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
        <init-param>
            <!--        定义模板的存储路径-->
            <param-name>TemplatePath</param-name>
            <param-value>/WEB-INF/ftl</param-value>
        </init-param>
        <init-param>
<!-- default_encoding用于设置读取ftl文件时采用的字符集,进而避免中文乱码的产生-->
            <param-name>default_encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>freemaker</servlet-name>
        <url-pattern>*.ftl</url-pattern>
    </servlet-mapping>
</web-app>
TestServlet.java
package com.imooc.oa.test;

import com.imooc.oa.util.MybatisUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "TestServlet", urlPatterns = "/test")
public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String result = (String)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("test.sample"));
        req.setAttribute("result",result);
        req.getRequestDispatcher("/test.ftl").forward(req,resp);
    }
}

RBAC(Role-Based Access Control)介绍

  • 基于**角色权限控制**(RBAC)是面向企业安全策略的访问控制方式

  • RBAC核心思想是将控制访问的资源与角色(Role)进行绑定

  • 系统的用户(User)与角色(Role)再进行绑定, 用户便拥有对应权限

一般主键cno或id都要设定字段类型为 BigInt
imooc-oa.sql

实现用户登录

基于LayUI开发登录页

LayUI前端框架

Layui - 经典开源模块化前端 UI 框架(官网文档镜像站) (layuiweb.com)

login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <link rel="stylesheet" href="/resources/layui-main/src/css/layui.css">
    <style>
        body {
            background-color: #f2f2f2;
        }

        .oa-container {
            /*background-color: white;*/
            position: absolute;
            width: 400px;
            height: 350px;
            top: 50%;
            left: 50%;
            padding: 20px;
            margin-left: -200px;
            margin-top: -175px;
        }
        #username,#password{
            /*text-align: center;*/
            /*font-size: 24px;*/
        }
    </style>
</head>
<body>
<div class="oa-container">
    <h1 style="text-align: center; margin-bottom: 20px">办公OA系统</h1>
    <form class="layui-form">
        <div class="layui-form-item">
            <input type="text" id="username" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input">
        </div>
        <div class="layui-form-item">
            <input type="password" id="password" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input">
        </div>
        <div class="layui-form-item">
            <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
        </div>
    </form>
</div>
</body>
</html>

实现用户登录-1

com.imooc.oa.entity.User
package com.imooc.oa.entity;

public class User {
    /*
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
     */
    private Long userId;
    private String username;
    private String password;
    private Long employeeId;
    Getter + Setter
}
com.imooc.oa.dao.UserDao
package com.imooc.oa.dao;

import com.imooc.oa.entity.User;
import com.imooc.oa.util.MybatisUtils;

/**
 * 用户表
 */
public class UserDao {
    /**
     * 按照用户名查询用户表
     * @param username 用户名
     * @return User对象包含对应的用户信息,null则代表对象不存在
     */
    public User selectByUsername(String username){
        User user = (User)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("usermapper.selectByUsername",username));
        return user;
    }
}
user.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="usermapper">
    <select id="selectByUsername" parameterType="String" resultType="com.imooc.oa.entity.User">
        select * from sys_user where username = #{value}
    </select>
</mapper>
mybatis-config.xml
<mappers>
    <mapper resource="mappers/test.xml"/>
    <mapper resource="mappers/user.xml"/>
</mappers>

Dao → Service

创建测试用例快捷键 Ctrl+Shift+T
UserSerive.java
package com.imooc.oa.serive;

import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.User;
import com.imooc.oa.serive.exception.BussinessException;

public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
    private UserDao userDao = new UserDao(); //实例化

    /**
     * 根据前台输入进行登录校验
     * @param username 前台输入的用户名
     * @param password 前台输入的密码
     * @return 校验通过后,包含对应用户数据的User实体类
     * @throws BussinessException L001-用户名不存在,L002-密码错误
     */ 
    public User checkLogin(String username, String password){
        User user = userDao.selectByUsername(username);
        if (user == null){
            //抛出用户不存在异常
            throw new BussinessException("L001", "用户名不存在");
        }
        if(!password.equals(user.getPassword())){
            throw new BussinessException("L002", "密码错误");
        }
        return user;
    }
}
test/serive.UserServiceTest.java
package com.imooc.oa.serive;

import junit.framework.TestCase;
import org.junit.Test;

public class UserServiceTest extends TestCase {
    private UserService userService = new UserService();

    @Test
    public void testCheckLogin1() {
        userService.checkLogin("uu","1234");
    }
    @Test
    public void testCheckLogin2() {
        userService.checkLogin("m8","1234");
    }
    @Test
    public void testCheckLogin3() {
        userService.checkLogin("uu","test");
    }
}
serive.exception.BussinessException.java
package com.imooc.oa.serive.exception;

/**
 * 业务逻辑异常
 */
public class BussinessException extends RuntimeException{
    private String code; //异常编码,异常的以为标识
    private String message; //异常的具体文本消息
    public BussinessException(String code, String msg){
        super(code + ":" + msg);
        this.code = code;
        this.message = msg;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

实现用户登录-2

com.imooc.oa.controller.LoginServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import com.imooc.oa.service.exception.BussinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "LoginServlet" ,urlPatterns = "/check_login")
public class LoginServlet extends HttpServlet {
    Logger logger = LoggerFactory.getLogger(LoginServlet.class);
    private UserService userService = new UserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //接收用户输入
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        Map<String, Object> result = new HashMap<>();
        try {
            //调用业务逻辑
            User user = userService.checkLogin(username, password);
            result.put("code", "0");
            result.put("message", "success");
        }catch (BussinessException ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getCode());
            result.put("message", ex.getMessage());
        }catch (Exception ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getClass().getSimpleName());
            result.put("message", ex.getMessage());
        }
        //返回对应结果
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

实现用户登录-3

pom.xml
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.62</version>
</dependency>
login.xml实现增添表单校验
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>慕课网办公OA系统</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
    <style>
        body{
            background-color: #F2F2F2;
        }
        .oa-container{
            /*background-color: white;*/
            position: absolute;
            width: 400px;
            height: 350px;
            top: 50%;
            left: 50%;
            padding: 20px;
            margin-left: -200px;
            margin-top: -175px;
        }
        #username,#password{
            /*text-align: center;*/
            /*font-size: 24px;*/
        }
    </style>
</head>
<body>
<div class="oa-container">
    <h1 style="text-align: center;margin-bottom: 20px">办公OA系统</h1>
    <form class="layui-form">
        <div class="layui-form-item">
            <input type="text" id="username" lay-verify="required" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input" >
        </div>

        <div class="layui-form-item">
            <input type="password" id="password" lay-verify="required" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input" >
        </div>
        <div class="layui-form-item">
            <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
        </div>
    </form>
</div>
<script src="/resources/layui/layui.all.js"></script>
<script>
    // 表单提交事件 表单输入校验 在上面加 lay-verify="requrired"
    layui.form.on("submit(login)" , function(formdata){//data参数包含了当前表单的数据
        console.log(formdata);
        //发送ajax请求进行登录校验
        layui.$.ajax({
            url : "/check_login",
            data : formdata.field, //提交表单数据
            type : "post",
            dataType : "json" ,
            success : function(json){
                console.log(json);
                if(json.code == "0"){ //登录校验成功 内置弹出层
                    layui.layer.msg("登录成功");
                }else{
                    layui.layer.msg(json.message);
                }
            }
        })
        return false;//submit提交事件返回true则表单提交,false则阻止表单提交
    })
</script>
</body>
</html>
</html>
后面通过Ajax[浏览器后台(上面return false)]请求向服务器发起异步通信获取校验是否通过

分析后台首页布局与设计

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    姓名[部门-职务]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="#">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <!--父节点-->
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">模块1</a>
                    <dl class="layui-nav-child module" data-node-id="1"></dl>
                </li>
                <!--子节点-->
                <dd class="function" data-parent-id="1">
                    <a href="javascript:void(0)" target="ifmMain">功能1</a>
                </dd>
                <dd class="function" data-parent-id="1">
                    <a href="javascript:void(0)" target="ifmMain">功能2</a>
                </dd>
                <dd class="function" data-parent-id="1">
                    <a href="javascript:void(0)" target="ifmMain">功能3</a>
                </dd>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">模块2</a>
                    <dl class="layui-nav-child module" data-node-id="2"></dl>
                </li>
                <dd class="function" data-parent-id="2">
                    <a href="javascript:void(0)" target="ifmMain">功能3</a>
                </dd>
                <dd class="function" data-parent-id="2">
                    <a href="javascript:void(0)" target="ifmMain">功能4</a>
                </dd>
                <dd class="function" data-parent-id="2">
                    <a href="javascript:void(0)" target="ifmMain">功能5</a>
                </dd>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>

动态显示功能菜单-1 【核心:rbac.xml

通过用户找到角色sys_user 再通过角色找到节点sys_role_user 接下来通过节点编号sys_role_node去获取与之对应的节点其他信息(三表关联)

xml→Dao→UserService

resources.mappers.rbac.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="rbacmapper">
    <select id="selectNodeByUserId" parameterType="Long" resultType="com.imooc.oa.entity.Node">
        select distinct n.*
        from
            sys_role_user ru, sys_role_node rn, sys_node n
        where
            ru.role_id = rn.role_id and user_id = #{value} and rn.node_id = n.node_id
        order by n.node_code
    </select>
</mapper>
mybatis-config.xml
    <mappers>
        <mapper resource="mappers/test.xml"/>
        <mapper resource="mappers/user.xml"/>
        <mapper resource="mappers/rbac.xml"/>
    </mappers>
imooc.oa.dao.RbacDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Node;
import com.imooc.oa.util.MybatisUtils;

import java.util.List;

public class RbacDao {
    public List<Node> selectNodeByUserId(Long userId){
        return (List)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectList("rbacmapper.selectNodeByUserId", userId));
    }
}
service.UserService.java
package com.imooc.oa.service;

import com.imooc.oa.dao.RbacDao;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.exception.BussinessException;

import java.util.List;

public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
    private UserDao userDao = new UserDao(); //实例化
    private RbacDao rbacDao = new RbacDao();

    /**
     * 根据前台输入进行登录校验
     * @param username 前台输入的用户名
     * @param password 前台输入的密码
     * @return 校验通过后,包含对应用户数据的User实体类
     * @throws BussinessException L001-用户名不存在,L002-密码错误
     */
    public User checkLogin(String username, String password){
        User user = userDao.selectByUsername(username);
        if (user == null){
            //抛出用户不存在异常
            throw new BussinessException("L001", "用户名不存在");
        }
        if(!password.equals(user.getPassword())){
            throw new BussinessException("L002", "密码错误");
        }
        return user;
    }
    public List<Node> selectNodeByUserId(Long userId){
        List<Node> nodeList = rbacDao.selectNodeByUserId(userId);
        return nodeList;
    }
}
UserServiceTest.java
@Test
    public void selectNodeByUserId(){
        List<Node> nodeList = userService.selectNodeByUserId(2l);
        System.out.println(nodeList);
    }
Node.java
public class Node {
    private Long nodeId;
    private Integer nodeType;
    private String nodeName;
    private String url;
    private Integer nodeCode;
    private Long parentId;
    Setter + Getter
}

动态显示功能菜单-2 (不同用户登录不同功能)

修改LoginServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import com.imooc.oa.service.exception.BussinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "LoginServlet" ,urlPatterns = "/check_login")
public class LoginServlet extends HttpServlet {
    Logger logger = LoggerFactory.getLogger(LoginServlet.class);
    private UserService userService = new UserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //接收用户输入
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        Map<String, Object> result = new HashMap<>();
        try {
            //调用业务逻辑 不能直接 request.setAttribute 选更大的对象
            User user = userService.checkLogin(username, password);
            HttpSession session = request.getSession();
            //session种存在用户信息 向session存入登录用户信息,属性名:login_user
            session.setAttribute("login_user", user);
            result.put("code", "0");
            result.put("message", "success");
            result.put("redirect_url", "/index"); //url登陆成功直接返回客户端
        }catch (BussinessException ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getCode());
            result.put("message", ex.getMessage());
        }catch (Exception ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getClass().getSimpleName());
            result.put("message", ex.getMessage());
        }
        //返回对应结果
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
oa.controller.IndexServlet.java
package com.imooc.oa.controller;

import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
//接收数据传入
@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
    private UserService userService = new UserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response){

    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("login_user");
        List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
        request.setAttribute("node_list",nodeList);
        request.getRequestDispatcher("/index.ftl").forward(request, response);
    }
}
将index.html 变成 index.ftl并放在Web/WEB-INF/ftl/index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    姓名[部门-职务]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="#">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <#list node_list as node>
                <!--父节点-->
                    <#if node.nodeType == 1>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">${node.nodeName}</a>
                    <dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
                </li>
                    </#if>
                    <#if node.nodeType == 2>
                <!--子节点-->
                <dd class="function" data-parent-id="${node.parentId}">
                    <a href="javascript:void(0)" target="ifmMain">${node.nodeName}</a>
                </dd>
                    </#if>
                </#list>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>

Xml配置下实现Mapper接口 (登录用户所对应员工)

接口 xml mapper增加 employeeservice

index.ftl indexServlet 改index.ftl

增加自动化部门 entity.Department dao.创建接口DepartmentDao mappers.department.xml -config.xml注册 DepartmentSerive.java
IndexServlet.java index.ftl

entity.Employee.java
public class Employee{
    private Long employeeId;
    private String name;
    private Long departmentId;
    private String title;
    private Integer level;
    Getter+Setter
}
dao.EmployeeDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Department;

public interface DepartmentDao {
    public Department selectById(Long departmentId);
}
mybatis-config.xml
<mapper resource="mappers/employee.xml"/>

index.ftl
 <!--右侧当前用户信息-->
   <ul class="layui-nav layui-layout-right">
       <li class="layui-nav-item">
           <a href="javascript:void(0)">
               <!--图标-->
              <span class="layui-icon layui-icon-user" style="font-size: 20px">
               </span>
               <!--用户信息-->
                    ${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
                </a>
            </li>
IndexServlet.java
package com.imooc.oa.controller;

import com.imooc.oa.entity.Department;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.DepartmentService;
import com.imooc.oa.service.EmployeeService;
import com.imooc.oa.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
    private UserService userService = new UserService();
    private EmployeeService employeeService = new EmployeeService();
    private DepartmentService departmentService = new DepartmentService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response){

    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        //得到当前登录用户对象
        User user = (User) session.getAttribute("login_user");
        Employee employee = employeeService.selectById(user.getEmployeeId());
        //获取登录用户可用功能模块列表
        List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
        //获取员工对应的部门
        Department department = departmentService.selectById(employee.getDepartmentId());
        //放入请求属性 session生存时间长
        request.setAttribute("node_list",nodeList);
        session.setAttribute("current_employee",employee);
        session.setAttribute("current_department", department);
        //请求派发至ftl进行展现
        request.getRequestDispatcher("/index.ftl").forward(request, response);
    }
}
增加自动化部门
entity.Department.java
public class Department {
    private Long departmentId;
    private String departmentName;
    Getter+Setter
}
dao.DepartmentDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Department;

public interface DepartmentDao {
    public Department selectById(Long departmentId);
}
mapper.department.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace与包名类名一致-->
<mapper namespace="com.imooc.oa.dao.DepartmentDao">
    <!--id与方法名对应 parameterType与方法参数类型对应 resultType与方法返回类型对应-->
    <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.Department">
        select * from adm_department where department_id = #{value}
    </select>
</mapper>

mybatis-config.xml
<mapper resource="mappers/department.xml"/>
service.DepartmentServlet.java
package com.imooc.oa.service;

import com.imooc.oa.dao.DepartmentDao;
import com.imooc.oa.entity.Department;
import com.imooc.oa.util.MybatisUtils;

public class DepartmentService {
    /**
     * 按编号得到部门对象
     * @param departmentId 部门编号
     * @return 部门对象,null代表部门不存在
     */
    public Department selectById(Long departmentId){
        return (Department) MybatisUtils.executeQuery(
                sqlSession -> sqlSession.getMapper(DepartmentDao.class).selectById(departmentId));
    }
}
controller.indexServlet.java
package com.imooc.oa.controller;

import com.imooc.oa.entity.Department;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.DepartmentService;
import com.imooc.oa.service.EmployeeService;
import com.imooc.oa.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
    private UserService userService = new UserService();
    private EmployeeService employeeService = new EmployeeService();
    private DepartmentService departmentService = new DepartmentService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response){

    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        //得到当前登录用户对象
        User user = (User) session.getAttribute("login_user");
        Employee employee = employeeService.selectById(user.getEmployeeId());
        //获取登录用户可用功能模块列表
        List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
        //获取员工对应的部门
        Department department = departmentService.selectById(employee.getDepartmentId());
        //放入请求属性 session生存时间长
        request.setAttribute("node_list",nodeList);
        session.setAttribute("current_employee",employee);
        session.setAttribute("current_department", department);
        //请求派发至ftl进行展现
        request.getRequestDispatcher("/index.ftl").forward(request, response);
    }
}
index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    ${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="#">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <#list node_list as node>
                <!--父节点-->
                    <#if node.nodeType == 1>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">${node.nodeName}</a>
                    <dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
                </li>
                    </#if>
                    <#if node.nodeType == 2>
                <!--子节点-->
                <dd class="function" data-parent-id="${node.parentId}">
                    <a href="javascript:void(0)" target="ifmMain">${node.nodeName}</a>
                </dd>
                    </#if>
                </#list>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>

基于MD5算法对密码加密

MD5摘要算法
  • MD5信息摘要算法广泛使用的密码散列函数
  • MD5可以产生出一个128位的散列值用于唯一标识源数据
  • 项目中通常使用MD5作为敏感数据的加密算法
MD5特点
  • 压缩性, MD5生成的摘要长度固定
  • 抗修改, 源数据哪怕有一个字节变化, MD5也会有巨大差异
  • 不可逆, 无法通过MD5反向推算源数据
Apache Commons Codec
  • Commons-Codec是Apache提供的编码/解码组件
  • 通过Commons-Codec可以轻易生成源数据的MD5摘要
  • MD5摘要方法: String md5 = DigestUtils.md5Hex (源数据)
util.MD5Utils.java
package com.imooc.oa.util;

import org.apache.commons.codec.digest.DigestUtils;

public class MD5Utils {
    public static String md5Digest(String source){
        return DigestUtils.md5Hex(source);
    }
}

敏感数据加盐混淆

md5utilstest userservice user

public class User {
    /*
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
     */
    private Long userId;
    private String username;
    private String password;
    private Long employeeId;
    private Integer salt;
    Getter + Setter
}
util.MD5Utils.java
package com.imooc.oa.util;

import org.apache.commons.codec.digest.DigestUtils;

public class MD5Utils {
    /**
     * 对数据源加盐混淆后生成MD5摘要
     * @param source 源数据
     * @return MD5摘要
     */
    public static String md5Digest(String source){
        return DigestUtils.md5Hex(source);
    }
    public static String md5Digest(String source, Integer salt){
        char[] ca = source.toCharArray(); //字符数组
        for (int i = 0; i < ca.length; i++) {
            ca[i] = (char)(ca[i] + salt);
        }
        String target = new String(ca);
//        System.out.println(target);
        String md5 = DigestUtils.md5Hex(target);
        return md5;
    }

    public static void main(String[] args) {
        System.out.println(MD5Utils.md5Digest("test", 188));
    }
}
修改UserService.java 中的密码校验
package com.imooc.oa.service;

import com.imooc.oa.dao.RbacDao;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.exception.BussinessException;
import com.imooc.oa.util.MD5Utils;

import java.util.List;

public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
    private UserDao userDao = new UserDao(); //实例化
    private RbacDao rbacDao = new RbacDao();

    /**
     * 根据前台输入进行登录校验
     * @param username 前台输入的用户名
     * @param password 前台输入的密码
     * @return 校验通过后,包含对应用户数据的User实体类
     * @throws BussinessException L001-用户名不存在,L002-密码错误
     */
    public User checkLogin(String username, String password){
        User user = userDao.selectByUsername(username);
        if (user == null){
            //抛出用户不存在异常
            throw new BussinessException("L001", "用户名不存在");
        }
        //对前台输入的密码加盐混淆后生成MD5,与保存在数据库中的MD5密码进行对比
        String md5 = MD5Utils.md5Digest(password, user.getSalt());
        if(!md5.equals(user.getPassword())){
            throw new BussinessException("L002", "密码错误");
        }
        return user;
    }
    public List<Node> selectNodeByUserId(Long userId){
        List<Node> nodeList = rbacDao.selectNodeByUserId(userId);
        return nodeList;
    }
}

实现注销功能

loginServlet保存着数据 indexservlet中的session保存着数据
清除session

oa.controller.LogoutServlet.java
package com.imooc.oa.controller;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "LogoutServlet", urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getSession().invalidate(); //会话注销
        response.sendRedirect("/login.html"); //跳转回去
    }
}
index.ftl
<!--注销按钮-->
<li class="layui-nav-item"><a href="/logout">注销</a></li>

请假流程数据库设计

开发多级审批流程
  • 公司所有员工都可以使用”请假申请”功能申请休假
  • 请假时间少于72小时,部门经理审批后直接通过
  • 请假时间大于72小时,部门经理审批后还需总经理进行审批
  • 部门经理只允许批准本部门员工申请
  • 部门经理请假需直接由总经理审批
  • 总经理提起请假申请,系统自动批准通过

工作流程表设计

请假单表LeaveForm → 审批任务流程表ProcessFlow → 消息通知表Notice

设计约束
  • 每一个请假单对应一个审批流程

  • 请假单创建后, 按业务规则生成部门经理、总经理审批任务

  • 审批任务的经办人只能审批自己辖区内的请假申请(总裁办可以审批所有 软件只能审批软件)

  • 所有审批任务”通过”, 代表请假已经批准

  • 任意审批任务”驳回”操作, 其余审批任务取消, 请假申请驳回

  • 请假流程中注意节点产生的操作都要生成对应的系统通知

实现Dao与数据交互

entity 数据新增接口dao(依靠Mybatis) 增加mapper 创造LeaveFormDaoTest
ProcessFlowDao + Test mapper
NoticeDao

entity.LeaveForm.java
public class LeaveForm {
    private Long formId;
    private Long employeeId;
    private Integer formType;
    private Date startTime;
    private Date endTime;
    private String reason;
    private Date createTime;
    private String state;
    Getter + Setter
}
dao.LeaveForm.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.LeaveForm;

public interface LeaveFormDao {
    public void insert(LeaveForm form);
}
mybatis-config.xml
<mapper resource="mappers/leave_form.xml"/>
test.dao.LeaveFormDaoTest.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;
import org.junit.Test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class LeaveFormDaoTest extends TestCase {
    @Test
    public void testInsert(){
        MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
            LeaveForm form = new LeaveForm();
            form.setEmployeeId(4L);//员工编号
            form.setFormType(1); //事假
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date startTime = null; //起始时间
            Date endTime = null; //结束时间
            try {
                startTime = sdf.parse("2020-03-25 08:00:00");
                endTime = sdf.parse("2020-04-01 18:00:00");
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
            form.setStartTime(startTime);
            form.setEndTime(endTime);
            form.setReason("回家探亲"); //请假事由
            form.setCreateTime(new Date()); //创建时间
            form.setState("processing"); //当前状态
            dao.insert(form);
            return null;
        });
    }

}

entity.ProcessFlow.java
package com.imooc.oa.entity;

import java.util.Date;

public class ProcessFlow {
    private Long processId;
    private Long formId;
    private Long operatorId;
    private String action;
    private String result;
    private String reason;
    private Date createTime;
    private Date auditTime;
    private Integer orderNo;
    private String state;
    private Integer isLast;
    Setter + Getter
}
dao.ProcessFlowDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.ProcessFlow;

public interface ProcessFlowDao {
    public void insert(ProcessFlow processFlow);
}
mybatis-config.xml
<mapper resource="mappers/process_flow.xml"/>
test.dao.ProcessFlowDaoTest.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;

import java.util.Date;

public class ProcessFlowDaoTest extends TestCase {
    public void testInsert(){
        MybatisUtils.executeQuery(sqlSession -> {
            ProcessFlowDao dao = sqlSession.getMapper(ProcessFlowDao.class);
            ProcessFlow flow = new ProcessFlow();
            flow.setFormId(31L);
            flow.setOperatorId(21L);
            flow.setAction("audit");
            flow.setReason("approved");
            flow.setReason("同意");
            flow.setCreateTime(new Date());
            flow.setAuditTime(new Date());
            flow.setOrderNo(1);
            flow.setState("ready");
            flow.setIsLast(1);
            dao.insert(flow);
            return null;
        });
    }
}

entity.Notice.java
public class Notice {
    private Long noticeId;
    private Long receiverId;
    private String content;
    private Date createTime;
    Getter + Setter
}
dao.NoticeDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Notice;

public interface NoticeDao {
    public void insert(Notice notice);
}
mybatis-config.xml
<mapper resource="mappers/notice.xml"/>
test.dao.NoticeDaoTest.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Notice;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;
import org.junit.Test;

import java.util.Date;

public class NoticeDaoTest extends TestCase {
    @Test
    public void testInsert(){
        MybatisUtils.executeQuery(sqlSession -> {
            NoticeDao dao = sqlSession.getMapper(NoticeDao.class);
            Notice notice = new Notice();
            notice.setReceiverId(21L);
            notice.setContent("测试消息");
            notice.setCreateTime(new Date());
            dao.insert(notice);
            return null;
        });
    }
}

实现请假申请业务逻辑-1

LeaveFormService employee.xml{动态审批sql}

employee.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace与包名类名一致-->
<mapper namespace="com.imooc.oa.dao.EmployeeDao">
    <!--id与方法名对应 parameterType与方法参数类型对应 resultType与方法返回类型对应-->
    <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.Employee">
        select * from adm_employee where employee_id = #{value}
    </select>
    <select id="selectLeader" parameterType="com.imooc.oa.entity.Employee" resultType="com.imooc.oa.entity.Employee">
        select * from adm_employee where
            <if test="emp.level &lt; 7">
                level = 7 and department_id = #{emp.departmentId}
            </if>
            <if test="emp.level == 7">
                level = 8;
            </if>
            <if test="emp.level == 8">
                employee_id = #{emp.employeeId}
            </if>
    </select>
</mapper>
service.LeaveFormService.java (重要业务走向代码不放在程序中)
package com.imooc.oa.service;

import com.imooc.oa.dao.EmployeeDao;
import com.imooc.oa.dao.LeaveFormDao;
import com.imooc.oa.dao.ProcessFlowDao;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.util.MybatisUtils;

import java.util.Date;

/**
 * 请假单流程服务
 */
public class LeaveFormService {
    /**
     * 创建请假单
     * @param form 前端输入的请假单数据
     * @return 持久化后的请假单对象
     */
    public LeaveForm createLeaveForm(LeaveForm form){
            LeaveForm savedForm = (LeaveForm) MybatisUtils.executeQuery(sqlSession -> {
            //1.持久化form表单数据,8级以下员工表单状态位processing,8级(总经理)状态位approved
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = employeeDao.selectById(form.getEmployeeId());
            if (employee.getLevel() == 8){
                form.setState("approved");
            }else {
                form.setState("processing");
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            leaveFormDao.insert(form);
            //2.增加第一条流程数据,说明表单已提交,状态位complete 初始化数据↓
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            ProcessFlow flow1 = new ProcessFlow();
            flow1.setFormId(form.getFormId());
            flow1.setOperatorId(employee.getEmployeeId());
            flow1.setAction("apply");
            flow1.setCreateTime(new Date());
            flow1.setOrderNo(1);
            flow1.setState("complete");
            flow1.setIsLast(0);
            processFlowDao.insert(flow1);
            //3.分情况创建其余流程数据 employee.xml(动态sql语句)
              //3.1 7级以下员工,生成部门经理审批任务,请假时间大于36小时(后期单独处理),还需生成总经理审批任务
            if (employee.getLevel() < 7){
                //动态sql EmployeeDao
                Employee dmanage = employeeDao.selectLeader(employee);
                ProcessFlow flow2 = new ProcessFlow();
                flow2.setFormId(form.getFormId());
                flow2.setOperatorId(dmanage.getEmployeeId());
                flow2.setAction("audit");//审批任务
                flow2.setCreateTime(new Date());
                flow2.setOrderNo(2);
                flow2.setState("process");
                long diff = form.getEndTime().getTime() - form.getStartTime().getTime(); //毫秒数
                float hours = diff/(1000*60*60) * 1f;
                if (hours >= BussinessConstants.MANAGER_AUDIT_HOURS){
                    flow2.setIsLast(0); //最后节点
                    processFlowDao.insert(flow2);
                    Employee manager = employeeDao.selectLeader(dmanage);//总经理
                    ProcessFlow flow3 = new ProcessFlow();
                    flow3.setFormId(form.getFormId());
                    flow3.setOperatorId(manager.getEmployeeId());
                    flow3.setAction("audit");
                    flow3.setCreateTime(new Date());
                    flow3.setState("ready");
                    flow3.setOrderNo(3);
                    flow3.setIsLast(1);
                    processFlowDao.insert(flow3);
                }else {//小于3天{
                    flow2.setIsLast(1);
                    processFlowDao.insert(flow2);
                }
            } else if (employee.getLevel() == 7) {//部门经理
                //3.2 7级员工,生成总经理审批任务
                Employee manager = employeeDao.selectLeader(employee);
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(manager.getEmployeeId());
                flow.setAction("audit");
                flow.setCreateTime(new Date());
                flow.setState("process");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
            }else if (employee.getLevel() == 8){
                //3.3 8级员工,生成总经理审批任务,系统自动通过
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(employee.getEmployeeId());
                flow.setAction("audit");
                flow.setResult("自动通过");
                flow.setCreateTime(new Date());
                flow.setAuditTime(new Date());
                flow.setState("complete");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
            }
            return form;
        });
        return savedForm;
    }
} 
service.BussinessConstants.java
package com.imooc.oa.service;

public class BussinessConstants {
    public static final int MANAGER_AUDIT_HOURS = 36; //总经理请假审批时间阈值
}

ctrl+shift+t 生成测试用例

Test.service.LeaveFormServiceTest.java
package com.imooc.oa.service;

import com.imooc.oa.entity.LeaveForm;
import org.junit.Test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import static org.junit.Assert.*;

public class LeaveFormServiceTest {
    LeaveFormService leaveFormService = new LeaveFormService();

    /**
     * 市场部员工请假单(72小时以上)测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm1() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(8l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1); //事假
        form.setReason("市场部员工请假单(72小时以上)");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }

    /**
     * 市场部员工请假单(72小时内)测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm2() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(8l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020032718"));
        form.setFormType(1);
        form.setReason("市场部员工请假单(72小时内)");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }

    /**
     * 研发部部门经理请假单测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm3() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(2l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1);
        form.setReason("研发部部门经理请假单");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }

    /**
     * 总经理请假单测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm4() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(1l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1);
        form.setReason("总经理请假单");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }
}

实现请假申请控制

Servlet 前后端整体交互(底层)

leaveformservlet

controller.LeaveFormServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.LeaveFormService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "LeaveFormServlet", urlPatterns = "/leave/*")
public class LeaveFormServlet extends HttpServlet {
    private LeaveFormService leaveFormService = new LeaveFormService();
    private Logger logger = LoggerFactory.getLogger(LoggerFactory.class);
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        // http://localhost/leave/create
        String uri = request.getRequestURI();
        String methodName = uri.substring(uri.lastIndexOf("/") + 1); //截取本身就包含斜杠
        if (methodName.equals("create")){

        }
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        this.doPost(request,response);
    }

    /**
     * 创建请假单
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void create(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("login_user");
        String formType = request.getParameter("formType");
        String strStartTime = request.getParameter("startTime");
        String strEndTime = request.getParameter("endTime");
        String reason = request.getParameter("reason");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH");
        Map result = new HashMap();
        try {
            LeaveForm form = new LeaveForm();
            form.setEmployeeId(user.getEmployeeId());
            form.setStartTime(sdf.parse(strStartTime));
            form.setEndTime(sdf.parse(strEndTime));
            form.setFormType(Integer.parseInt(formType));
            form.setReason(reason);
            form.setCreateTime(new Date());
            //2.调用业务逻辑方法
            leaveFormService.createLeaveForm(form);
            result.put("code", "0");
            result.put("message", "success");
        } catch (Exception e){
            logger.error("请假申请异常",e);
            result.put("code", e.getClass().getSimpleName());
            result.put("message", e.getMessage());
        }
        //3.组织相应数据
        String json = JSON.toJSONString(result); //将result转换为字符串
        response.getWriter().println(json);
    }
}

完整实现请假申请功能

controller.ForwardServlet.java
package com.imooc.oa.controller;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 页面跳转Servlet
 */
@WebServlet(name="ForwardServlet", urlPatterns = "/forward/*")
public class ForwardServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String uri = request.getRequestURI();
        /** 动态提取 把第一个/抛出外 再去寻找第一个‘/’
         * /forward/form
         * /forward/a/b/c/form
         */
        String subUri = uri.substring(1);
        String page = subUri.substring(subUri.indexOf("/"));
        request.getRequestDispatcher(page + ".ftl").forward(request,response); //扩展名 + web.xml映射路径
    }
}
form.html 变换为 form.ftl 放在ftl内

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>请假申请</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
    <style>
        /*表单容器*/
        .ns-container {
            position: absolute;
            width: 500px;
            height: 450px;
            top: 150px;
            left: 50%;
            margin-left: -250px;
            padding: 20px;
            box-sizing: border-box;
            border: 1px solid #cccccc;
        }
    </style>
</head>
<body>
<div class="layui-row">
    <blockquote class="layui-elem-quote">
        <h2>请假申请</h2>
    </blockquote>
    <table id="grdNoticeList" lay-filter="grdNoticeList"></table>
</div>
<div class="ns-container">
    <h1 style="text-align: center;margin-bottom: 20px">请假申请单</h1>
    <form class="layui-form">
        <!--基本信息-->
        <div class="layui-form-item">
            <label class="layui-form-label">部门</label>
            <div class="layui-input-block">
                <div class="layui-col-md12" style="padding-top: 10px;">
                    研发部
                </div>

            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">申请人</label>
            <div class="layui-input-block">
                <div class="layui-col-md12" style="padding-top: 10px;">
                    ${current_employee.name}[${current_employee.title}]
                </div>

            </div>
        </div>
        <!--请假类型下拉框-->
        <div class="layui-form-item">
            <label class="layui-form-label">请假类别</label>
            <div class="layui-input-block layui-col-space5">
                    <select name="formType" lay-verify="required" lay-filter="cityCode">
                        <option value="1">事假</option>
                        <option value="2">病假</option>
                        <option value="3">工伤假</option>
                        <option value="4">婚嫁</option>
                        <option value="5">产假</option>
                        <option value="6">丧假</option>
                    </select>
            </div>
        </div>
        
        <!--请假时长日期选择框-->
        <div class="layui-form-item">
            <label class="layui-form-label">请假时长</label>
            <div class="layui-input-block layui-col-space5">
                    <input name="leaveRange" type="text" class="layui-input" id="daterange" placeholder=" - ">
                    <input id="startTime" name="startTime" type="hidden">
                    <input id="endTime" name="endTime" type="hidden">
            </div>
        </div>

        <!--请假事由-->
        <div class="layui-form-item">
            <label class="layui-form-label">请假事由</label>
            <div class="layui-input-block layui-col-space5">
                    <input name="reason" type="text"  lay-verify="required|mobile" placeholder="" autocomplete="off" class="layui-input">
            </div>
        </div>

        <!--提交按钮-->
        <div class="layui-form-item " style="text-align: center">
                <button class="layui-btn" type="button" lay-submit lay-filter="sub">立即申请</button>
        </div>
    </form>
</div>

<script src="/resources/layui/layui.all.js"></script>
<!--Sweetalert2对话框-->
<script src="/resources/sweetalert2.all.min.js"></script>

<script>
        var layDate = layui.laydate; //Layui日期选择框JS对象
        var layForm = layui.form; //layui表单对象
        var $ = layui.$; //jQuery对象
        //日期时间范围
        layDate.render({
            elem: '#daterange'  //daterange渲染成日期选择框
            ,type: 'datetime'
            ,range: true
            ,format: 'yyyy年M月d日H时'
            ,done: function(value, start, end){
                //选择日期后出发的时间,设置startTime与endTime隐藏域
                var startTime = start.year + "-" + start.month + "-" + start.date + "-" + start.hours;
                var endTime = end.year + "-" + end.month + "-" + end.date + "-" + end.hours;
                console.info("请假开始时间",startTime);
                $("#startTime").val(startTime);
                console.info("请假结束时间",endTime);
                $("#endTime").val(endTime);
            }
        });

        //表单提交时间
        layForm.on('submit(sub)', function(data){
            console.info("向服务器提交的表单数据",data.field);
            $.post("/leave/create",data.field,function (json) {
                console.info("服务器返回数值",json);
                if(json.code == "0"){
                    /*SweetAlert2确定对话框*/
                    swal({
                        type: 'success',
                        html: "<h2>请假单已提交,等待上级审批</h2>",
                        confirmButtonText: "确定"
                    }).then(function (result) {
                        window.location.href="/forward/notice";
                    });
                }else{
                    swal({
                        type: 'warning',
                        html: "<h2>" + json.message + "</h2>",
                        confirmButtonText: "确定"
                    });
                }
            },"json");
            return false;
        });

</script>
</body>
</html>
把localhost/index 主页添加数据
index.ftl中的47行到50行
 <!--子节点-->
<dd class="function" data-parent-id="${node.parentId}">
 <a href="#{node.url}" target="ifmMain">${node.nodeName}</a>
</dd>

请假审批功能

leave_form.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.oa.dao.LeaveFormDao">
    <insert id="insert" parameterType="com.imooc.oa.entity.LeaveForm"
        useGeneratedKeys="true" keyProperty="formId" keyColumn="form_id">
        INSERT INTO adm_leave_form( employee_id, form_type, start_time, end_time, reason, create_time, state)
        VALUES ( #{employeeId}, #{formType}, #{startTime}, #{endTime}, #{reason}, #{createTime}, #{state})
    </insert>
    <select id="selectByParams" parameterType="java.util.Map" resultType="java.util.Map">
        select f.* ,e.name , d.*
        from
          adm_leave_form f,adm_process_flow pf , adm_employee e , adm_department d
        where
          f.form_id = pf.form_id
          and f.employee_id = e.employee_id
          and e.department_id = d.department_id
          and pf.state = #{pf_state} and pf.operator_id = #{pf_operator_id}
    </select>
<!--    <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.LeaveForm">-->
<!--        select * from adm_leave_form where form_id = #{value}-->
<!--    </select>-->

<!--    <update id="update" parameterType="com.imooc.oa.entity.LeaveForm">-->
<!--        UPDATE adm_leave_form SET employee_id = #{employeeId} , form_type = #{formType}, start_time = #{startTime}, end_time = #{endTime}, reason = #{reason}, state = #{state} ,create_time = #{createTime} WHERE form_id = #{formId}-->
<!--    </update>-->
</mapper>
dao.LeaveFormDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.LeaveForm;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface LeaveFormDao {
    public void insert(LeaveForm form);
    public List<Map> selectByParams(@Param("pf_state") String pfState , @Param("pf_operator_id") Long operatorId);
} 
Test
  @Test
    public void testSelectByParams(){
        MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
            List<Map> list = dao.selectByParams("process", 21L);
            System.out.println(list);
            return list;
        });
    }
controller.LeaveFormServlet.java
   /**
     * 查询需要审核的请假单列表
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void getLeaveFormList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        User user = (User) request.getSession().getAttribute("login_user");
        List<Map> formList = leaveFormService.getLeaveFormList("process", user.getEmployeeId());
        Map result = new HashMap();
        result.put("code","0"); //服务器处理成功
        result.put("msg",""); //服务器返回具体处理消息
        result.put("count", formList.size()); //数据总数
        result.put("data", formList); //当前显示的对象页表
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

实现待审批请假列表

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>请假审批</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
    <style>
        .form-item{
            padding: 10px;
        }
        .form-item-value{
            padding: 10px;
        }
    </style>
</head>
<body>
<div class="layui-row">
    <blockquote class="layui-elem-quote">
        <h1>请假审批</h1>
    </blockquote>
    <!--待审批列表-->
    <table id="grdFormList" lay-filter="grdFormList"></table>
</div>
<!--请假详情对话框-->
<div id="divDialog" style="display: none;padding: 10px">
    <form class="layui-form">

        <div class="layui-form-item">
            <div class="layui-row">
                <div class="layui-col-xs2 form-item">部门</div>
                <div class="layui-col-xs4 form-item-value" id="dname"></div>
                <div class="layui-col-xs2 form-item">姓名</div>
                <div class="layui-col-xs4 form-item-value" id="name"></div>
            </div>
            <div class="layui-row">
                <div class="layui-col-xs2 form-item">起始时间</div>
                <div class="layui-col-xs4 form-item-value" id="startTime"></div>
                <div class="layui-col-xs2 form-item">结束时间</div>
                <div class="layui-col-xs4 form-item-value" id="endTime"></div>
            </div>
            <div class="layui-row">
                <div class="layui-col-xs2 form-item">请假原因</div>
                <div class="layui-col-xs10 form-item-value" id="reason"></div>
            </div>
            <!--表单Id-->
            <input type="hidden" name="formId" id="formId">
            <!--审批结果-->
            <select name="result" lay-verfity="required">
                <option value="approved">同意</option>
                <option value="refused">驳回</option>
            </select>
        </div>
        <div class="layui-form-item">
            <!--审批意见-->
            <input type="text" name="reason" placeholder="请输入审批意见"
                   autocomplete="off" class="layui-input"/>
        </div>
        <div class="layui-form-item">
            <button class="layui-btn layui-btn-fluid " lay-submit lay-filter="audit">确认提交</button>

        </div>
    </form>
</div>

<script src="/resources/layui/layui.all.js"></script>
<script src="/resources/sweetalert2.all.min.js"></script>

<script>
    var $ = layui.$;
    //将毫秒数转换为"yyyy-MM-dd HH时"字符串格式
    function formatDate(time){
        var newDate = new Date(time);
        return newDate.getFullYear() + "-" +
            (newDate.getMonth() + 1) + "-" + newDate.getDate()
            + " " + newDate.getHours() + "时";
    }
    // 将table渲染为数据表格
    layui.table.render({
        elem : "#grdFormList" , //选择器
        id : "grdFormList" , //id
        url : "/leave/list" , //ajax请求url
        page : false , //是否分页 true-是 false-否
        cols :[[ //列描述
            {title : "" , width:70 , style : "height:60px" , type:"numbers"}, // numbers代表序号列
            {field : "create_time" , title : "申请时间" , width : 150 , templet: function (d) {
                //templet代表对数据进行加工后再显示
                return formatDate(d.create_time)
            }},
            {field : "form_type" , title : "类型" , width : 100 , templet: function(d){
                switch (d.form_type) {
                    case 1:
                        return "事假";
                    case 2:
                        return "病假";
                    case 3:
                        return "工伤假";
                    case 4:
                        return "婚假";
                    case 5:
                        return "产假";
                    case 6:
                        return "丧假";
                }
            }},
            {field : "department_name" , title : "部门" , width : 100},
            {field : "name" , title : "员工" , width : 100},
            {field : "start_time" , title : "起始时间" , width : 150, templet: function (d) {
                    return formatDate(d.start_time)
                }},
            {field : "end_time" , title : "结束时间" , width : 150 , templet: function (d) {
                    return formatDate(d.end_time)
                }},
            {field : "reason" , title : "请假原因" , width : 350 },
            {title : "" , width:150 ,type:"space" , templet : function(d){
                var strRec = JSON.stringify(d);
                console.info("请假单数据", d);
                console.info("请假单数据", strRec);
                //将请假单数据存放至data-laf属性中
                return "<button class='layui-btn layui-btn-danger layui-btn-sm btn-audit' data-laf=" + strRec + " >审批</button>";
            }}
        ]]
    })

    // 绑定每一行的审批按钮
    $(document).on("click" , ".btn-audit" , function(){
        //初始化表单
        $("#divDialog form")[0].reset();
        $("#divDialog form form-item-value").text("");
        //获取当前点击按钮的请假单数据,回填至显示项 json对象 内置数据显示页面
        var laf = $(this).data("laf");
        $("#dname").text(laf.department_name);
        $("#name").text(laf.name);
        $("#startTime").text(formatDate(laf.start_time));
        $("#endTime").text(formatDate(laf.end_time));
        $("#reason").text(laf.reason);
        $("#formId").val(laf.form_id);
        //弹出layui对话框
        layui.layer.open({
            type : "1" , //页面层
            title : "请假审批" , //标题
            content : $("#divDialog") , //指定对话框容器对象
            area : ["500px" , "400px"] , //尺寸
            end : function(){ //销毁后触发事件
                $("#divDialog").hide();
            }
        })
    })
    /**
     * 提交审批数据 本质:发送Ajax请求
     */
    layui.form.on("submit(audit)" , function(data){
        $.ajax({
            url : "/leave/audit", //审核URL
            data : data.field ,
            type : "post" ,
            dataType : "json" ,
            success: function (json) {
                //关闭所有layui对话框
                layui.layer.closeAll();
                //显示处理结果
                if(json.code == "0"){
                    swal({
                        type: 'success',
                        html: "<h2>请假已审批完毕</h2>",
                        confirmButtonText: "确定"
                    }).then(function (result) {
                        window.location.href="/forward/notice";
                    });
                }else{
                    swal({
                        type: 'warning',
                        html: "<h2>" + json.message + "</h2>",
                        confirmButtonText: "确定"
                    });
                }
            }
        })
        return false;
    })

</script>
</body>
</html>

实现审批业务逻辑

service.LeaveFormService.java
 public void audit(Long formId, Long operatorId, String result, String reason){
        MybatisUtils.executeQuery(sqlSession -> {
            //1.无论同意/驳回, 当前任务状态变更为complete
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            List<ProcessFlow> flowList = processFlowDao.selectByFormId(formId);
            if (flowList.size() == 0){
                throw new BussinessException("PF001", "无效的审批流程");//自定义错误抛出
            }
            //获取当前任务ProcessFlow对象
            List<ProcessFlow> processList = flowList.stream().filter(p->p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
            ProcessFlow process = null;
            if (processList.size() == 0){
                throw new BussinessException("PF002", "未找到待处理任务");
            }else{
                process = processList.get(0);
                process.setState("complete");
                process.setResult(result);
                process.setReason(reason);
                process.setAuditTime(new Date());
                processFlowDao.update(process);
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            LeaveForm form = leaveFormDao.selectById(formId);
            //2.如果当前任务是最后一个节点,代表流程结束,更新请假单状态对应的approved/refused
            if (process.getIsLast() == 1){
                form.setState(result); // approved\refused
                leaveFormDao.update(form);
            }else {
                //readyList包含所有后续任务节点
                List<ProcessFlow> readyList = flowList.stream().filter(p->p.getState().equals("ready")).collect(Collectors.toList());
                //3.如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
                if (result.equals("approved")){
                    ProcessFlow readyProcess = readyList.get(0);
                    readyProcess.setState("process");
                    processFlowDao.update(readyProcess);
                } else if (result.equals("refused")) {
                    //4.如果当前任务不是最后一个切点且审核驳回,则后续所有任务状态变为cancel,请假单状态变为refused
                    for (ProcessFlow p : readyList){
                        p.setState("cancel");
                        processFlowDao.update(p);
                    }
                    form.setState("refused");
                    leaveFormDao.update(form);
                }
            }
            return null;
        });
    }
test.LeaveFormServiceTest.java (在数据库中增加新建查询 导入训练素材的 请假单审核测试数据.sql)
   /**
     * 请假3天以上,部门经理审批通过
     */
    @Test
    public void audit1(){
        leaveFormService.audit(31l,2l,"approved","祝早日康复");
    }

    /**
     * 请假3天以上,部门经理审批驳回
     */
    @Test
    public void audit2(){
        leaveFormService.audit(32l,2l,"refused","工期紧张,请勿拖延");
    }

    /**
     * 部门经理请假,总经理审批通过
     */
    @Test
    public void audit3(){
        leaveFormService.audit(33l,1l,"approved","同意");
    }

完整实现请假审批

controller.LeaveFormServlet.java
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        // http://localhost/leave/create
        String uri = request.getRequestURI();
        String methodName = uri.substring(uri.lastIndexOf("/") + 1); //截取本身就包含斜杠
        if (methodName.equals("create")){
            this.create(request,response);
        }else if (methodName.equals("list")){
            this.getLeaveFormList(request,response);
        } else if (methodName.equals("audit")) {
            this.audit(request,response);
        }
    }
...
...
...
  /**
     * 处理审批操作
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void audit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String formId = request.getParameter("formId");
        String result = request.getParameter("result");
        String reason = request.getParameter("reason");
        User user = (User) request.getSession().getAttribute("login_user");
        Map mpResult = new HashMap();
        try {
            leaveFormService.audit(Long.parseLong(formId), user.getEmployeeId(), result,reason);
            mpResult.put("code", "0");
            mpResult.put("message", "success");

        } catch (Exception e) {
            logger.error("请假单审核失败", e);
            mpResult.put("code", e.getClass().getSimpleName());
            mpResult.put("message", e.getMessage());
        }
        String json = JSON.toJSONString(mpResult);
        response.getWriter().println(json);
    }

实现系统消息业务逻辑

entity.Notice.java
    private Long noticeId;
    private Long receiverId;
    private String content;
    private Date createTime;
    public Notice(){

    }
    public Notice(Long receiverId, String content){
        this.receiverId = receiverId;
        this.content = content;
        this.createTime = new Date();
    }
    Getter + Setter
service.LeaveFormService.java
package com.imooc.oa.service;

import com.imooc.oa.dao.EmployeeDao;
import com.imooc.oa.dao.LeaveFormDao;
import com.imooc.oa.dao.NoticeDao;
import com.imooc.oa.dao.ProcessFlowDao;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.service.exception.BussinessException;
import com.imooc.oa.util.MybatisUtils;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 请假单流程服务
 */
public class LeaveFormService {
    /**
     * 创建请假单
     * @param form 前端输入的请假单数据
     * @return 持久化后的请假单对象
     */
    public LeaveForm createLeaveForm(LeaveForm form){
            LeaveForm savedForm = (LeaveForm) MybatisUtils.executeQuery(sqlSession -> {
            //1.持久化form表单数据,8级以下员工表单状态位processing,8级(总经理)状态位approved
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = employeeDao.selectById(form.getEmployeeId());
            if (employee.getLevel() == 8){
                form.setState("approved");
            }else {
                form.setState("processing");
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            leaveFormDao.insert(form);
            //2.增加第一条流程数据,说明表单已提交,状态位complete 初始化数据↓
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            ProcessFlow flow1 = new ProcessFlow();
            flow1.setFormId(form.getFormId());
            flow1.setOperatorId(employee.getEmployeeId());
            flow1.setAction("apply");
            flow1.setCreateTime(new Date());
            flow1.setOrderNo(1);
            flow1.setState("complete");
            flow1.setIsLast(0);
            processFlowDao.insert(flow1);
            //3.分情况创建其余流程数据 employee.xml(动态sql语句)
                // 3.1 7级以下员工,生成部门经理审批任务,请假时间大于36小时(后期单独处理),还需生成总经理审批任务
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
                NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
            if (employee.getLevel() < 7){
                //动态sql EmployeeDao
                Employee dmanager = employeeDao.selectLeader(employee);
                ProcessFlow flow2 = new ProcessFlow();
                flow2.setFormId(form.getFormId());
                flow2.setOperatorId(dmanager.getEmployeeId());
                flow2.setAction("audit");//审批任务
                flow2.setCreateTime(new Date());
                flow2.setOrderNo(2);
                flow2.setState("process");
                long diff = form.getEndTime().getTime() - form.getStartTime().getTime(); //毫秒数
                float hours = diff/(1000*60*60) * 1f;
                if (hours >= BussinessConstants.MANAGER_AUDIT_HOURS){
                    flow2.setIsLast(0); //最后节点
                    processFlowDao.insert(flow2);
                    Employee manager = employeeDao.selectLeader(dmanager);//总经理
                    ProcessFlow flow3 = new ProcessFlow();
                    flow3.setFormId(form.getFormId());
                    flow3.setOperatorId(manager.getEmployeeId());
                    flow3.setAction("audit");
                    flow3.setCreateTime(new Date());
                    flow3.setState("ready");
                    flow3.setOrderNo(3);
                    flow3.setIsLast(1);
                    processFlowDao.insert(flow3);
                }else {//小于3天{
                    flow2.setIsLast(1);
                    processFlowDao.insert(flow2);
                }
                //请假单已提交消息
                String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级审批."
                        , sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
                //通知部门经理审批消息
                noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
                        employee.getTitle() , employee.getName() ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(dmanager.getEmployeeId(),noticeContent));
            } else if (employee.getLevel() == 7) {//部门经理
                //3.2 7级员工,生成总经理审批任务
                Employee manager = employeeDao.selectLeader(employee);
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(manager.getEmployeeId());
                flow.setAction("audit");
                flow.setCreateTime(new Date());
                flow.setState("process");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
                //请假单已提交消息
                String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级审批."
                        , sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
                //通知总经理审批消息
                noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
                        employee.getTitle() , employee.getName() ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(manager.getEmployeeId(),noticeContent));
            }else if (employee.getLevel() == 8){
                //3.3 8级员工,生成总经理审批任务,系统自动通过
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(employee.getEmployeeId());
                flow.setAction("audit");
                flow.setResult("自动通过");
                flow.setCreateTime(new Date());
                flow.setAuditTime(new Date());
                flow.setState("complete");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
                String noticeContent = String.format("您的请假申请[%s-%s]系统已自动批准通过." ,
                        sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
            }
            return form;
        });
        return savedForm;
    }
    /**
     * 获取指定任务状态及指定经办人对应的请假单列表
     * @param pfState ProcessFlow任务状态
     * @param operatorId 经办人编号
     * @return 请假单及相关数据列表
     */
    public List<Map> getLeaveFormList(String pfState, Long operatorId){
        return (List<Map>)MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
            List<Map> formList = dao.selectByParams(pfState, operatorId);
            return formList;
        });
    }
    /**
     * 审核请假单
     * @param formId 表单编号
     * @param operatorId 经办人(当前登录员工)
     * @param result 审批结果
     * @param reason 审批意见
     */
    public void audit(Long formId, Long operatorId, String result, String reason){
        MybatisUtils.executeQuery(sqlSession -> {
            //1.无论同意/驳回, 当前任务状态变更为complete
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            List<ProcessFlow> flowList = processFlowDao.selectByFormId(formId);
            if (flowList.size() == 0){
                throw new BussinessException("PF001", "无效的审批流程");//自定义错误抛出
            }
            //获取当前任务ProcessFlow对象
            List<ProcessFlow> processList = flowList.stream().filter(p->p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
            ProcessFlow process = null;
            if (processList.size() == 0){
                throw new BussinessException("PF002", "未找到待处理任务");
            }else{
                process = processList.get(0);
                process.setState("complete");
                process.setResult(result);
                process.setReason(reason);
                process.setAuditTime(new Date());
                processFlowDao.update(process);
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            LeaveForm form = leaveFormDao.selectById(formId);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = employeeDao.selectById(form.getEmployeeId());//表单提交人信息
            Employee operator = employeeDao.selectById(operatorId);//任务经办人信息
            NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
            //2.如果当前任务是最后一个节点,代表流程结束,更新请假单状态对应的approved/refused
            if (process.getIsLast() == 1){
                form.setState(result); // approved\refused
                leaveFormDao.update(form);
                String strResult = null;
                if (result.equals("aproved")){
                    strResult = "批准";
                } else if (result.equals("refused")) {
                    strResult = "驳回";
                }
                String noticeContent = String.format("您的请假申请[%s-%s]%s%s已%s,审批意见:%s,审批流程已结束"
                        sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                        operator.getTitle(),operator.getName(), //批准\驳回
                        strResult,reason);//发给表单提交人的通知
                noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent));
            }else {
                //readyList包含所有后续任务节点
                List<ProcessFlow> readyList = flowList.stream().filter(p->p.getState().equals("ready")).collect(Collectors.toList());
                //3.如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
                if (result.equals("approved")){
                    ProcessFlow readyProcess = readyList.get(0);
                    readyProcess.setState("process");
                    processFlowDao.update(readyProcess);
                    //消息1: 通知表单提交人,部门经理已经审批通过,交由上级继续审批
                    String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已批准,审批意见:%s ,请继续等待上级审批" ,
                            sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                            operator.getTitle() , operator.getName(),reason);
                    noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent1));

                    //消息2: 通知总经理有新的审批任务
                    String noticeContent2 = String.format("%s-%s提起请假申请[%s-%s],请尽快审批" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()));
                    noticeDao.insert(new Notice(readyProcess.getOperatorId(),noticeContent2));

                    //消息3: 通知部门经理(当前经办人),员工的申请单你已批准,交由上级继续审批
                    String noticeContent3 = String.format("%s-%s提起请假申请[%s-%s]您已批准,审批意见:%s,申请转至上级领导继续审批" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
                    noticeDao.insert(new Notice(operator.getEmployeeId(),noticeContent3));
                } else if(result.equals("refused")) {
                    //4.如果当前任务不是最后一个节点且审批驳回,则后续所有任务状态变为cancel,请假单状态变为refused
                    for(ProcessFlow p:readyList){
                        p.setState("cancel");
                        processFlowDao.update(p);
                    }
                    form.setState("refused");
                    leaveFormDao.update(form);
                    //消息1: 通知申请人表单已被驳回
                    String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已驳回,审批意见:%s,审批流程已结束" ,
                            sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                            operator.getTitle() , operator.getName(),reason);
                    noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent1));

                    //消息2: 通知经办人表单"您已驳回"
                    String noticeContent2 = String.format("%s-%s提起请假申请[%s-%s]您已驳回,审批意见:%s,审批流程已结束" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
                    noticeDao.insert(new Notice(operator.getEmployeeId(),noticeContent2));
                }
            }
            return null;
        });

完整实现系统消息功能

dao.NoticeDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Notice;

import java.util.List;

public interface NoticeDao {
    public void insert(Notice notice);
    public List<Notice> selectByReceiverId(Long receiverId);
}
service.NoticeService.java
package com.imooc.oa.service;

import com.imooc.oa.dao.NoticeDao;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.util.MybatisUtils;

import java.util.List;

/**
 * 消息服务
 */
public class NoticeService {
    /**
     * 查询指定员工的系统消息
     * @param receiverId
     * @return 最近100条消息列表
     */
    public List<Notice> getNoticeList(Long receiverId){
        return (List) MybatisUtils.executeQuery(sqlSession -> {
            NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
            return noticeDao.selectByReceiverId(receiverId);
        });
    }
}
controller.NoticeServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.NoticeService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@WebServlet(name = "NoticeServlet" , urlPatterns = "/notice/list")
public class NoticeServlet extends HttpServlet {
    private NoticeService noticeService = new NoticeService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        User user = (User)request.getSession().getAttribute("login_user");
        List<Notice> noticeList = noticeService.getNoticeList(user.getEmployeeId());
        Map result = new HashMap<>();
        result.put("code", "0");
        result.put("msg", "");
        result.put("count", noticeList.size());
        result.put("data", noticeList);
        String json = JSON.toJSONString(result);
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().println(json);
    }
}
resources.mappers.notice.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.oa.dao.NoticeDao">
    <insert id="insert" parameterType="com.imooc.oa.entity.Notice"
            useGeneratedKeys="true" keyProperty="noticeId" keyColumn="notice_id">
        INSERT INTO sys_notice( receiver_id, content, create_time) VALUES (#{receiverId}, #{content}, #{createTime})
    </insert>

    <select id="selectByReceiverId" parameterType="Long" resultType="com.imooc.oa.entity.Notice">
        select * from sys_notice where receiver_id = #{value} order by create_time desc limit 0,100
    </select>
</mapper>
index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    ${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="/logout">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <#list node_list as node>
                <!--父节点-->
                    <#if node.nodeType == 1>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">${node.nodeName}</a>
                    <dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
                </li>
                    </#if>
                    <#if node.nodeType == 2>
                <!--子节点-->
                <dd class="function" data-parent-id="${node.parentId}">
                    <a href="${node.url}" target="ifmMain">${node.nodeName}</a>
                </dd>
                    </#if>
                </#list>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" src="/forward/notice" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>
notice.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>系统通知</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>
<body>
<div class="layui-row">
    <blockquote class="layui-elem-quote">
        <h2>系统通知</h2>
    </blockquote>
    <table id="grdNoticeList" lay-filter="grdNoticeList"></table>
</div>

<script src="/resources/layui/layui.all.js"></script>

<script>
    layui.table.render({
        elem : "#grdNoticeList" ,
        id : "grdNoticeList" ,
        url : "/notice/list" ,
        page : false ,
        cols :[[
            {field : "" , title : "序号" , width:"10%" , style : "height:60px" , type:"numbers"},
            {field : "create_time" , title : "通知时间" , width : "20%" , templet: function (d) {
                    var newDate = new Date(d.createTime);
                    return newDate.getFullYear() + "-" +
                        (newDate.getMonth() + 1) + "-" + newDate.getDate()
                        + " " + newDate.getHours() + ":" + newDate.getMinutes() + ":" + newDate.getSeconds();
                }},
            {field : "content" , title : "通知内容" , width : "60%"}
        ]]
    })

</script>
</body>
</html>
阅读全文

MyBatis基础与进阶

2023/10/30

MyBatis[ORM]框架基础

软件开发中框架

  • 框架式可被应用开发者定制的应用骨架
  • 框架是一种规则,保证开发者遵循相同的方式开发程序
  • 框架提倡”不要重复造轮子”,对基础功能进行封装

框架的优点

  • 极大提高了开发效率
  • 统一的编码规则,利于团队管理
  • 灵活配置的应用,拥有更好的维护性

SSM开发框架

  • Spring对象容器框架(框架的框架) [提供底层对象的管理]
  • SpringMVC替代servlet更有效的开发 [提供Web界面的交互]
  • MyBatis简化数据库的开发 [数据库增删改查便捷操作]

MyBatis

  • MyBatis是优秀的持久层框架(将内存中的数据保存到数据库中 以防止重启后数据丢失)
  • MyBatis使用XML将SQL与程序解耦,便于维护[改代码 改xml更方便]
  • MyBatis是JDBC的延伸

MyBatis开发流程【推荐使用Maven】

  • 引入MyBatis依赖
  • 创建核心配置文件
  • 创建实体(Entity)类
  • 创建Mapper映射文件
  • 初始化SessionFactory(绘画工厂 读取配置文件 加载Mapper映射)
  • 利用SqlSession对象操作数据

单元测试与JUnit4

单元测试(用于测试方法的方法)
  • 单元测试是指对软件中的最小可测试单元进行检查和验证
  • 测试用例是指写一段代码对已有功能(方法)进行校验
  • JUnit4是Java中最著名的单元测试工具,主流IDE内置支持

JUnit4使用方法

  • 引入JUnit Jar包或增加Maven依赖
  • 编写测试用例验证目标方法是否正确运行
  • 在测试用例上增加**@Test**注解开始单元测试

如果插件plug报错 点settings 找到 Build,Execution,Deployment → Build Tools → Maven 修改下面的三个地址到Maven

Maven home path: D:/apache-maven-3.9.5
User settings files: D:\apache-maven-3.9.5\conf\settings.xml
Local repository: D:\apache-maven-3.9.5\repository

Maven工程有专门测试用例的 test包
方法命名:在原方法前增加test前缀 Class命名:在原有命名后增加Test

快速生成类的测试用例 => 选中类 → Code → Generate → Test
按住Ctrl可以多选执行多个测试用例类
如果想运行所有测试用例类 工程上点右键 → Run ‘All Test’

Calculator.java
package org.example;

public class Calculator {
    public int add(int a, int b){
        return a + b;
    }
    public int substract(int a, int b){
        return a - b;
    }
    public int multiply(int a, int b){
        return a * b;
    }
    public float divide(int a, int b){
        if (b==0){
            throw new ArithmeticException("除数不能为0");
        }
        return (a*1f) / b;
    }
}
CalculatorTest.java
import org.example.Calculator;
import org.junit.Test;

public class CalculatorTest {
    private Calculator cal = new Calculator();
    @Test
    public void testAdd(){
        int result = cal.add(1,2);
        System.out.println(result);
    }
    @Test
    public void testSubstract(){
        int result = cal.substract(1,2);
        System.out.println(result);
    }
    @Test
    public void testMultiply(){
        int result = cal.multiply(1,2);
        System.out.println(result);
    }
    @Test
    public void testDivide(){
        float result = cal.divide(1,2);
        System.out.println(result);
    }
    @Test
    public void testDivide1(){
        float result = cal.divide(1,0);
        System.out.println(result);
    }
}

MyBatis环境配置

mybatis-config.xml
  • MyBatis采用XML格式配置数据库环境信息
  • MyBatis环境配置标签**< environment >**
  • environment包含数据库驱动、URL、用户名与密码
mybatis-config.xml
<!--配置环境,不同的环境不同的id名字-->
<enviroment id="dev">
    <!--采用JDBC方式对数据库事务进行commit/rollback-->
    <transactionManager type="JDBC"></transactionManager>
    <!--采用连接池方式管理数据库连接-->
    <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/db"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </dataSource>
</enviroment>
<groupId>org.example</groupId>
    <artifactId>JUnit4_Maven</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>http://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
    </dependencies>

可以在右侧加载内置Database数据库

<configuration>
    <environments default=""> 
        <environment id="">
            <transactionManager type="JDBC"></transactionManager> 
            <dataSource type=""></dataSource>
        </environment>
    </environments>
</configuration>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <environments default="dev"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type=""></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTFF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

SqlSessionFactory

  • SqlSessionFactory是MyBatis的核心对象
  • 用于初始化MyBatis, 创建SqlSession对象
  • SqlSession对象提供了数据表CRUD对应方法
  • 保证SqlSessionFactory在应用中全局唯一
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <environments default="prd"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
test-MyBatisTestor.java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;

//JUNIT单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//按照字符流读取
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try{
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
}
保证SqlSessionFactory在应用中全局唯一 [创建一个工具类]

static块用于初始化静态对象

MyBatisUtils.java
package com.imooc.mybatis.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;
//MaBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
public class MyBatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    static{ //利用静态块在初始化类时实例化sqlSessionFactory
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = null;//按照字符流读取
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        } catch (IOException e) {
            e.printStackTrace();
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

    public static void closeSession(SqlSession session){
        if (session != null){
            session.close();
        }
    }
}
MyBatisTestor.java
import com.imooc.mybatis.utils.MyBatisUtils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;

//JUNIT单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//按照字符流读取
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try{
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
    @Test
    public void testMyBatisUtils() throws Exception{
        SqlSession sqlSession = null;
        try{
            sqlSession = MyBatisUtils.openSession();
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }
}

MyBatis数据查询

用测试类去测试代码
test-java-MyBatisTestor.java
import com.imooc.mybatis.MyBatisUtils;
import com.imooc.mybatis.entity.Goods;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.util.List;

//JUNIT单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//按照字符流读取
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try{
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
    /**
     * MyBatisUtils使用指南
     * @throws Exception
     */
    @Test
    public void testMyBatisUtils() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

    /**
     * select查询语句执行
     * @throws Exception
     */
    @Test
    public void testSelectAll() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<Goods> list = session.selectList("goods.selectAll");
            for(Goods g : list){
                System.out.println(g.getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
}
MyBatis数据查询步骤
  • 创建实体类(Entity) /工具类(Utils)
Goods.java
private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    Getter+Setter...
MyBatisUtils.java
package com.imooc.mybatis;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;
//MaBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
public class MyBatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    static{ //利用静态块在初始化类时实例化sqlSessionFactory
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = null;//按照字符流读取
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        } catch (IOException e) {
            e.printStackTrace();
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * openSession 创建一个新的SqlSession对象
     * @return SqlSession对象
     */
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

    /**
     * 释放一个有效的SqlSession对象
     * @param session 准备释放SqlSession对象
     */
    public static void closeSession(SqlSession session){
        if(session != null){
            session.close();
        }
    }
}
  • 创建Mapper XML (在resources下创建) 表属性和字段一一对应

若要让mybatis认识xml 要在mybatis-config.xml里面声明 增加< mappers >

goods.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods"> <!--区分不同的工作空间-->
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods order by goods_id desc limit 10<!--将每一条记录包装成↑的goods对象-->
    </select>
</mapper>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <environments default="prd"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/goods.xml"/>
    </mappers>
</configuration>
  • 编写< select >SQL标签
goods.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods"> <!--区分不同的工作空间-->
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods order by goods_id desc limit 10<!--将每一条记录包装成↑的goods对象-->
    </select>
</mapper>
  • 开启驼峰命名映射
<settings><!--开启驼峰命名转换 goods_id → goodsId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
  • 新增< mapper >
mybatis-config.xml
<mappers>
        <mapper resource="mappers/goods.xml"/>
    </mappers>
  • SqlSession执行select语句

SQL传参[动态传入数据]

goods.xml
<select id="selectById" resultType="com.imooc.mybatis.entity.Goods">
    select * from t_goods where goods_id = #{value}
</select>

MyBatisTestor.java
@Test
    public void testSelectById() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1602);
            System.out.println(goods.getTitle());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
goods.xml
<select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
            select * from t_goods
            where
                current_price between #{min} and #{max}
            order by current_price
            limit 0,#{limit}
    </select>
        
MyBatisTestor.java (多参数传递要指定Map接口)
@Test
    public void selectByPriceRange() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            param.put("min", 100);
            param.put("max", 500);
            param.put("limit", 10);
            List<Goods> list = session.selectList("goods.selectByPriceRange",param);
            for (Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

获取多表关联查询结果[Map]

LinkedHashMap链表形式的HashMap不会出现乱序

利用LinkedHashMap保存多表关联结果
MyBatis会将每一条记录包装为LinkedHashMap对象
key是字段名,value是字段对应的值,字段类型根据表结构进行自动判断
优点:易于扩展,易于使用
缺点:太过灵活,无法进行编译时检查

goods.xml
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>
</mapper>
MyBatisTestor.java [map类 或 实体类]
@Test
    public void selectGoodsMap() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<Map> list = session.selectList("goods.selectGoodsMap");
            for (Map map : list){
                System.out.println(map);
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
}

ResultMap结果映射[多表查询结果 多人协作首选]

把复杂的查询结果映射成DTO对象来进行保存 调用的时候轻松获得属性 [书写大量的映射规则]
  • ResultMap可以将查询结果映射为复杂类型的Java对象
  • ResultMap适用于Java对象保存多表关联结果
  • ResultMap支持对象关联查询等高级特性

结果用java对象进行保存 dto包[GoodsDTO]是数据传输对象包[扩展包] 为了扩展的需要可以扩展

结果映射规则进行赋值 转换为哪个dto
查询出来后每次都会給goods中的goodsId进行赋值
用GoodsDTO.java 来承载多表关联查询的结果

GoodsDTO.java
public class GoodsDTO {
    private Goods goods = new Goods();
    private String categoryName;
    private String test;
    Getter+Setter
}

Goods.java
public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
}
MyBatisTestor.java
@Test
    public void selectGoodsDTO() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list){
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
Goods.xml
<resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <id property="goods.goodsId" column="goods_id"></id>
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="categoryName" column="category_name"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

GoodsDTO.java
public class GoodsDTO {
    private Goods goods = new Goods();
    private Category category = new Category();
    private String test;
    Getter+Setter...
}

Category.java
public class Category { //标准的Java Bean 换DTO的  private Category category = new Category();
    private Integer categoryId;
    private String categoryName;
    private Integer parentId;
    private Integer categoryLevel;
    private Integer categoryOrder;
    Getter+Setter...
}
Goods.xml
<resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <id property="goods.goodsId" column="goods_id"></id>
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="category.categoryId" column="category_id"/>
        <result property="category.categoryName" column="category_name"/>
        <result property="category.parentId" column="parent_id"/>
        <result property="category.categoryLevel" column="category_level"/>
        <result property="category.categoryOrder" column="category_order"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.*,'1' from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>
MyBatisTestor.java
@Test
    public void selectGoodsDTO() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list){
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

MyBatis数据插入操作

数据库事务
  • 数据库事务是保证数据操作完整性的基础

客户端[Java] → 事务日志[增删改查数据] → 向MySQL写入commit → 数据表作为更新数据 【若执行roll back后事务日志和数据表的数据都会被清除】

MyBatis写操作包含三种
  • 插入< insert >
goods.xml 新增操作
 <!--flushCache="true"在sql执行后强制清空缓存-->
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods"flushCache="true">
 INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
 VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
  <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER"> <!--主键自动生成-->
    select last_insert_id()
  </selectKey>
</insert>
MyBatisTestor.java
@Test
    public void selectInsert() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            //insert()方法返回值代表本次成功插入的记录总数
            int num = session.insert("goods.insert", goods);
            session.commit();
            System.out.println(goods.getGoodsId());
        }catch (Exception e){
            if (session != null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
  • 更新 < update>
    不推荐goods.set…来插入,而是推荐使用获取到原始的商品信息Goods goods = session.selectOne("goods.selectById", 739);再在原始信息上做出调整和更新。对数据影响最小
goods.xml
 <update id="update" parameterType="com.imooc.mybatis.entity.Goods">
        UPDATE t_goods
        SET
          title = #{title},
          sub_title = #{subTitle},
          original_cost = #{originalCost},
          current_price = #{currentPrice},
          discount = #{discount},
          is_free_delivery = #{isFreeDelivery},
          category_id = #{categoryId}
        WHERE
          good_id = #{goodsId} <!--对主键进行筛选-->
    </update>
MyBatisTestor.java
 @Test
    public void testUpdate() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 739);//得到指定编号的对象
            goods.setTitle("更新测试商品");
            int num = session.update("goods.update", goods);
            session.commit();
        }catch (Exception e){
            if (session!=null)
                session.rollback();
        }
    }
  • 删除 < delete >
    大多数删除操作都是根据主键来运行的
good.xml
<delete id="delete" parameterType="integer">
    delete from t_goods where goods_id = #{value}
</delete>
MyBatisTestor.java
 @Test
    public void Delete(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            int num = session.delete("goods.delete", 739);
            session.commit();
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

SelectKeyUserGeneratedKeys的区别

SelectKey属性用法
<insert id="insert" parameterType="com.itlaoqi.mybatis.entity.Goods">
  INSERT INTO SQL语句
============================获取主键值===============================(↑案例)
 <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
   select last_insert_id()
 </selectKey>
</insert>

★ selectKey标签需要明确编写获取最新主键的SQL语句  获取主键 ★
★ selectKey适用于所有的关系型数据库 ★
★ selectKey标签是通用方案,适用于所有数据库,但编写麻烦 ★
UserGeneratedKeys属性用法
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods">
   userGeneratedKeys="true"
   keyProperty="goodsId" <!--主键值-->
   keyColumn="goods_id"> <!--字段名-->
  INSERT INTO SQL语句
</insert>
★ useGeneratedKeys属性会自动根据驱动生成对应SQL语句 自动获取主键 ★
★ useGeneratedKeys只支持"自增主键"类型的数据库 ★
★ useGeneratedKeys属性只支持"自增主键"数据库,使用简单
在Oracle中selectKey的用法
<insert id="insert" parameterType="com.itlaoqi.mybatis.entity.Goods">
  InSERT INTO SQL语句
  <selectKey resultType="Integer" order=“BEFORE” keyProperty="goodsId">
      SELECT seq_goods.nextval as id from dual  
  </selectKey>
</insert>

SQL注入攻击[缺少转义操作]

  • SQL注入是指攻击者利用SQL漏洞,绕过系统约束,越权获取数据的攻击方式
SQLd代码:
"select * from a where name ='" + name + " '";

正常情况下:
name:张三 → select * from a where name = '张三';

SQL注入攻击:
name:' or 1=1 or 1='
select * from a whrer name = " or 1=1 or 1="
MyBatis两种传值方式
  • ${}文本替换(原文传值)(产生的sql语句绝不能是外界输入的), 未经过任何处理对SQL文本替换 {根据前台输入的条件不同来选择不同的字段排序 没有对输入的数据进行预编译处理 导致输入的内容变成了sql的一部分}

    ${order}
    param.put("order"," order by title desc");

    用${}原传值:select * from t_goods where title = ‘ ‘ or 1 =1 or title = ‘【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版’”); 成为sql的一部分

  • #{}预编译传值, 使用预编译传值可以预防SQL注入

    用#{}预编译:select * from t_goods where title = “ ‘ ‘ or 1 =1 or title = ‘【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版’”); #输入的会变成字符串

goods.xml
<select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
  select * from t_goods where title = ${title}
</select>
MyBatisTestor.java
 @Test
    public void testSelectByTitle(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            pparam.put("title","'【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            param.put("order", " order by title desc");
//param.put("titile","'' or 1=1 or title='【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
//用${}原传值:select * from t_goods where title = '' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'"); 成为sql的一部分
//用#{}预编译:select * from t_goods where title = "'' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            List<Goods> list = session.selectList("goods.selectByTitle", param);
            for(Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

使用上传照片

MyBatis进阶教程

MyBatis日志管理

什么是日志
  • 日志文件是用于记录系统操作事件的记录文件或文件集合
  • 日志保存历史数据, 是诊断问题以及理解系列活动的重要依据
ch.qos.logback logback-classic 1.2.3

上传照片

goods.xml
<select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where title = #{title}
            ${order}
    </select>
MyBatisTestor.java
@Test
    public void testSelectByTitle(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            param.put("title","'【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            param.put("order", " order by title desc");
//param.put("titile","'' or 1=1 or title='【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
//用${}原传值:select * from t_goods where title = '' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'"); 成为sql的一部分
//用#{}预编译:select * from t_goods where title = "'' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            List<Goods> list = session.selectList("goods.selectByTitle", param);
            for(Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
   }
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
<!--            23:39:16.761 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.-->
        </encoder>
    </appender>
    <!--
        日志输出级别(优先级高到低):
        error: 错误 - 系统的故障日志
        warn: 警告 - 存在风险或使用不当的日志
        info: 一般性消息
        debug: 程序内部用于调试信息
        trace: 程序运行的跟踪信息
     -->
<!--生产环境最低级别设置info以上 开发环境最低级别设置debug以上方便调试-->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

MyBatis动态SQL

动态SQL
  • 动态SQL是指根据参数数据动态组织SQL的技术
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com...Goods">
    select * from t_goods
    where     <!--动态增加SQL的数据-->
        <if test="categoryId != null">
            and category_id=#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
</select>
goods.xml
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        <where>    <!--根据查询条件动态增加SQL的数据-->
        <if test="categoryId != null">
            and category_id=#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
        </where>
    </select>
<where></where>  where标签会动态的对子sql进行判断

MyBatis二级缓存 (可共享对象)

sql语句第一次查询 sql存储在硬盘上
优化:第一次查询的时候放在某个内存 再次访问就很快 (缓存)

  • 一级缓存默认开启,缓存范围SqlSession会话
  • 二级缓存手动开启,属于范围Mapper Namespace

二级缓存运行规则

  • 二级开启后默认所有查询操作均使用缓存
  • 写操作commit提交时对该namespace缓存强制清空
  • 配置useCache=false可以不用缓存
  • 配置flushCache=true代表强制清空缓存
@Test
public void testLv1Cache(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            session.commit();//commit提交时对该namespace缓存强制清空
            Goods goods1 = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode() + ":" + goods1.hashCode());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

Goods goods = session.selectOne(“goods.selectById”, 1603);
session.commit();//commit提交时对该namespace缓存强制清空
缓存和语句距离太短 资源浪费 使用率不高哦

<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>

运行结果只出现一条sql语句 结果的goods.hasCode的结果来自同一块内存区域
MyBatisTestor.java
 @Test
    public void testLv2Cache(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }

        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

08:59:50.304 [main] DEBUG goods - Cache Hit Ratio [goods]: 0.5
缓存命中率越高 证明优化越好
goods.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods"> <!--区分不同的工作空间-->
    <!--开启了二级缓存
        eviction是缓存的清除策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除
        1. LRU - 最近最久未使用:移除最长随时间不被使用的对象{893}
           LFU - 最近时间内方位最少的移除{1}
        【LRU + LFU 增强缓存速度】
        01 02 03 04 .. 0512
        14 99 83 1        893
        2. FIFO - 先进先出:按对象进入缓存的顺序来移除它们
        3. SOFT - 软引用:移除基于垃圾收集器状态和软引用规则的对象
        4. WEAK - 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象
        flushInterval 代表间隔多长时间自动清空缓存,单位毫秒,600000毫秒=10分钟
        size 缓存存储上限,用于保存对象或集合(1个结婚算1个对象)的数据上限
        readOnly 设置为true,代表返回只读缓存,每次从缓存取出的是缓存对象本身,这种执行效率较高
                 设置为false,代表每次取出的是缓存对象的"副本",每一次取出的对象都是不同的,这种安全性较高
    -->
    <cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
    <!--useCache="false"代表查询结果不放入缓存-->
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="false">
        select * from t_goods order by goods_id desc limit 10<!--将每一条记录包装成↑的goods对象-->
    </select>
    <select id="selectById" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where goods_id = #{value}
    </select>

    <select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
            select * from t_goods
            where
                current_price between #{min} and #{max}
            order by current_price
            limit 0,#{limit}
    </select>
<!--flushCache="true"在sql执行后强制清空缓存 效果和commit相同--> 
    <select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

    <resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <id property="goods.goodsId" column="goods_id"></id>
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="category.categoryId" column="category_id"/>
        <result property="category.categoryName" column="category_name"/>
        <result property="category.parentId" column="parent_id"/>
        <result property="category.categoryLevel" column="category_level"/>
        <result property="category.categoryOrder" column="category_order"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.*,'1' from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

    <!--flushCache="true"在sql执行后强制清空缓存 效果和commit相同--> 
    <insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" flushCache="true">
        INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
        <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER"> <!--主键自动生成-->
        select last_insert_id()
        </selectKey>
    </insert>

    <update id="update" parameterType="com.imooc.mybatis.entity.Goods">
        UPDATE t_goods
        SET
          title = #{title},
          sub_title = #{subTitle},
          original_cost = #{originalCost},
          current_price = #{currentPrice},
          discount = #{discount},
          is_free_delivery = #{isFreeDelivery},
          category_id = #{categoryId}
        WHERE
          good_id = #{goodsId} <!--对主键进行筛选-->
    </update>

    <delete id="delete" parameterType="integer">
        delete from t_goods where goods_id = #{value}
    </delete>

    <select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where title = #{title}
            ${order}
    </select>

    <select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        <where>    <!--根据查询条件动态增加SQL的数据-->
        <if test="categoryId != null">
            and category_id=#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
        </where>
    </select>
</mapper>
一级缓存被默认开启的随着sql开 随着sql关

复习<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/> 当中的各种

<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" flushCache="true">

MyBatis多表级联查询 (通过一个对象获得另外一个对象)

一个比较有多个学生 而 一个学生在同一时间只能隶属于一个班级

实体关系分析

商品和详情对象关联查询 [商品是1 详情是多 详情那方要持有商品的主键]

ManyToOne对象关联查询 [collection]

mybatis-config.xml 最底下加一层
<mappers>
   <mapper resource="mappers/goods.xml"/>
   <mapper resource="mappers/goods_detail.xml"/>
</mappers>


good.xml
 <!--
        resultMap可以用于说明一对多或者多对一的映射逻辑
        id 是 resultMap属性引用标志
        type 指向One的实体(Goods)
    -->
    <resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
        <!-- 映射goods对象的主键到goods_id字段 -->
        <id column="goods_id" property="goodsId"></id>
        <!--
            collection的含义是,在
            select * from t_goods limit 0,1 得到结果后,对所有Goods对象遍历得到goods_id字段值,
            并代入到goodsDetail命名空间的findByGoodsId的SQL中执行查询,
            将得到的"商品详情"集合赋值给goodsDetails List对象.
        -->
        <collection property="goodsDetails" select="goodsDetail.selectByGoodsId"
                    column="goods_id"/>
    </resultMap>
    <select id="selectOneToMany" resultMap="rmGoods1">
        select * from t_goods limit 0,10
    </select>


goods_details.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goodsDetail">
    <select id="selectByGoodsId" parameterType="Integer"
            resultType="com.imooc.mybatis.entity.GoodsDetail">
        select * from t_goods_detail where goods_id = #{value}
    </select>
</mapper>
Goods.java 再加一个
public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    private List<GoodsDetail> goodsDetails;
    Getter+Setter
}
MaBatisTestor.java 
/**
     * 测试多对一对象关联映射
     */
    @Test
    public void testOneToMany() throws Exception {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSession();
            List<Goods> list = session.selectList("goods.selectOneToMany");
            for(Goods goods:list) {
                System.out.println(goods.getTitle() + ":" + goods.getGoodsDetails().size());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

ManyToOne对象关联查询 [association]

商品和详情对象关联查询
goods_detail.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goodsDetail">
    <select id="selectByGoodsId" parameterType="Integer"
            resultType="com.imooc.mybatis.entity.GoodsDetail">
        select * from t_goods_detail where goods_id = #{value}
    </select>

    <resultMap id="rmGoodsDetail" type="com.imooc.mybatis.entity.GoodsDetail">
        <id column="gd_id" property="gdId"/>
        <!--字段映射-->
        <result column="goods_id" property="goodsId"/>
        <!--从多的一方关联单的一方     在goods.xml中的goods空间 根据查询结果id带入到这个语句赋值到goods语句-->
        <association property="goods" select="goods.selectById" column="goods_id"></association>
    </resultMap>
    <select id="selectManyToOne" resultMap="rmGoodsDetail">
        select * from t_goods_detail limit 0,20
    </select>
</mapper>
MyBatisTestor.java
 @Test
    public void testManyToOne() throws Exception {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSession();
            List<GoodsDetail> list = session.selectList("goodsDetail.selectManyToOne");
            for(GoodsDetail gd:list) {
                System.out.println(gd.getGdPicUrl() + ":" + gd.getGoods().getTitle());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }
GoodsDetail.java
public class GoodsDetail {
    private Integer gdId;
    private Integer goodsId;
    private String gdPicUrl;
    private Integer gdOrder;
    private Goods goods;
    Getter + Setter
}

分页查询的麻烦事

  • 当前页数据查询 - select * from tab limit 0,10
  • 总记录数查询 - select count(*) from tab
  • 程序计算总页数、上一页页码、下一页页码
分页插件PageHelper

PageHelper使用流程

  • maven引入PageHelper与jsqlparser
  • mybatis-config.xml增加Plugin配置
  • 代码中使用PageHelper.startPage()自动分页
pom.xml
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId> <!--最核心处理的sql语句-->
    <version>2.0</version>
</dependency>


goods.xml
<select id="selectPage" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where current_price &lt; 1000
</select>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <settings><!--开启驼峰命名转换 goods_id → goodsId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--启动Pagehelper分页插件-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--设置数据库类型-->
            <property name="helperDialect" value="mysql"/>
            <!--分页合理化-->
            <property name="reasonable" value="true"/>
        </plugin>
    </plugins>
    <environments default="dev"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/goods.xml"/>
        <mapper resource="mappers/goods_detail.xml"/>
    </mappers>
</configuration>
MyBatisTestor.java
@Test
    public void testSelectPage() throws Exception {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSession();
            /*startPage方法自动将下一次查询进行分页*/
            PageHelper.startPage(2,10);
            Page<Goods> page = (Page)session.selectList("goods.selectPage");
            System.out.println("总页数:" + page.getPages());
            System.out.println("总记录数:" + page.getTotal());
            System.out.println("开始行号:" + page.getStartRow());
            System.out.println("结束行号:" + page.getEndRow());
            System.out.println("当前页码:" + page.getPageNum());
            List<Goods> data = page.getResult();//当前页数据
            for (Goods g : data) {
                System.out.println(g.getTitle());
            }
            System.out.println("");
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

不同数据库分页的实现原理 [面试/笔试]

MySQL分页 select * from table limit 10,20; 起始行号 向后取多少值
Oracle分页(三层嵌套)

Oracle分页(三层嵌套)
select t3.* from(
    select t2.*,rownum as row_num from(
        select * from table order by id asc
    ) t2 where rownum <= 20
)t3
where t2.row_num>11

SQL Server 2000

select top 3 * from table
where
  id not in
  (select top 15 id from table)

SQL Server 2012+

select * from table order by id 
   offset 4 rows fetch next 5 rows only

MyBatis配置C3P0连接池

pom.xml
<dependency>
     <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.4</version>
</dependency>
mybatis-config.xml
<environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
<!--            <dataSource type="POOLED">-->
            <dataSource type="com.imooc.mybatis.datasource.C3P0DataSourceFactory">
                <property name="driverClass" value="com.mysql.jdbc.Driver"/>
                <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="user" value="root"/>
                <property name="password" value="root"/>
                <property name="initialPoolSize" value="5"/>
                <property name="maxPoolSize" value="20"/>
                <property name="minPoolSize" value="5"/>
                <!--...-->
            </dataSource>
        </environment>
C3P0DataSourceFactory.java
package com.imooc.mybatis.datasource;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

/**
 * C3P0与MyBatis兼容使用的数据源工厂类
 */
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory{
    public C3P0DataSourceFactory(){
        this.dataSource = new ComboPooledDataSource();
    }
}

MyBatis批处理 [利用集合保存批处理数据] [海量数据导入]

goods.xml
<!--    INSERT INTO table-->
<!--    VALUES("a","a1","a2"),("b","b1","b2"),(...)-->
<insert id="batchInsert" parameterType="java.util.List">
     INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
     VALUES
    <foreach collection="list" item="item" index="index" separator=",">
      (#{item.title},#{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
    </foreach>
<!--批量插入数据的局限:1.无法获得插入数据的id  2.批量生成的SQL太长,可能会被服务器拒绝(可以分段2次for循环)-->
</insert>
MyBatisTestor.java
/**
     * 批量插入测试
     * @throws Exception
     */
    @Test
    public void testBatchInsert() throws Exception {
        SqlSession session = null;
        try {
            long st = new Date().getTime();
            session = MyBatisUtils.openSession();
            List list = new ArrayList();
            for (int i = 0; i < 10000; i++) {
                Goods goods = new Goods();
                goods.setTitle("测试商品");
                goods.setSubTitle("测试子标题");
                goods.setOriginalCost(200f);
                goods.setCurrentPrice(100f);
                goods.setDiscount(0.5f);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                //insert()方法返回值代表本次成功插入的记录总数

                list.add(goods);
            }
            session.insert("goods.batchInsert", list);
            session.commit();//提交事务数据
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et - st) + "毫秒");
//            System.out.println(goods.getGoodsId());
        } catch (Exception e) {
            if (session != null) {
                session.rollback();//回滚事务
            }
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

====================================================================
 /**
     * 10000次数据插入对比测试用例
     * @throws Exception
     */
    @Test
    public void testInsert1() throws Exception {
        SqlSession session = null;
        try{
            long st = new Date().getTime();
            session = MyBatisUtils.openSession();
            List list = new ArrayList();
            for(int i = 0 ; i < 10000 ; i++) {
                Goods goods = new Goods();
                goods.setTitle("测试商品");
                goods.setSubTitle("测试子标题");
                goods.setOriginalCost(200f);
                goods.setCurrentPrice(100f);
                goods.setDiscount(0.5f);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                //insert()方法返回值代表本次成功插入的记录总数
                //每循环一次 插入一次
                session.insert("goods.insert" , goods);
            }

            session.commit();//提交事务数据
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et-st) + "毫秒");
//            System.out.println(goods.getGoodsId());
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

MyBatis常用注解 (注解适合小型敏捷项目 XML适合大型协作项目)

注解 对应XML 说明
@Insert < insert > 更新SQL
@Update < update > 更新SQL
@Delete < delete > 删除SQL
@Select < select > 查询SQL
@Param 参数映射
@Results < resultMap > 结果映射
@Result < id > < result > 字段映射
把XML中的写入代码中
dao.GoodsDAO.java
package com.imooc.mybatis.dao;

import com.imooc.mybatis.dto.GoodsDTO;
import com.imooc.mybatis.entity.Goods;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface GoodsDAO {
    @Select("select * from t_goods where current_price between #{min} and #{max} order by current_price limit 0,#{limit}")
    public List<Goods> selectByPriceRange(@Param("min") Float min, @Param("max") Float max, @Param("limit") Integer limit);

    @Insert("INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id) VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})")
    //<selectKey> befor是true则在这之前执行 keyProperty主键 resultType返回主键类型
    @SelectKey(statement = "select last_insert_id()" , before = false , keyProperty = "goodsId" , resultType = Integer.class)
    public int insert(Goods goods);

    @Select({"select * from t_goods"})
    //<resultMap>
    @Results({ //select实行完以下结果 按照以下规则并赋值給GoodsDTO对象
            //<id>
            @Result(column = "goods_id", property = "goodsId", id = true),
            //<result>
            @Result(column = "title", property = "title"),
            @Result(column = "current_price", property = "currentPrice"),
    })
    public List<GoodsDTO> selectAll();

}
dto.GoodsDTO
package com.imooc.mybatis.dto;

public class GoodsDTO {
    private Integer goodsId;//商品编号
    private String title;//标题
    private Float currentPrice;//当前价格

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Float getCurrentPrice() {
        return currentPrice;
    }

    public void setCurrentPrice(Float currentPrice) {
        this.currentPrice = currentPrice;
    }
}
entity.Goods.java
public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    Getter + Setter
}
resources.logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
           <pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>

    <!--
        日志输出级别(优先级高到低):
        error: 错误 - 系统的故障日志
        warn: 警告 - 存在风险或使用不当的日志
        info: 一般性消息
        debug: 程序内部用于调试信息
        trace: 程序运行的跟踪信息
     -->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>
resources.mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- goods_id ==> goodsId 驼峰命名转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--设置默认指向的数据库-->
    <environments default="dev">
        <!--配置环境,不同的环境不同的id名字-->
        <environment id="dev">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
<!--        <mapper class="com.imooc.mybatis.dao.GoodsDAO"/>-->
        <package name="com.imooc.mybatis.dao"/>
    </mappers>
</configuration>
test.java.com.imooc.mybatis.MyBatisTestor
package com.imooc.mybatis;

import com.imooc.mybatis.dao.GoodsDAO;
import com.imooc.mybatis.dto.GoodsDTO;
import com.imooc.mybatis.entity.Goods;
import com.imooc.mybatis.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

//JUNIT单元测试类
public class MyBatisTestor {

    @Test
    public void testSelectByPriceRange() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class); //包含注解的接口
            List<Goods> list = goodsDAO.selectByPriceRange(100f, 500f, 20);
            System.out.println(list.size());
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

    /**
     * 新增数据
     * @throws Exception
     */
    @Test
    public void testInsert() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
            //insert()方法返回值代表本次成功插入的记录总数
            int num = goodsDAO.insert(goods);
            session.commit();//提交事务数据
            System.out.println(goods.getGoodsId()); //最新的数据回填給goodsId
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

    @Test
    public void testSelectAll() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
            List<GoodsDTO> list = goodsDAO.selectAll();
            System.out.println(list.size());
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.imooc</groupId>
    <artifactId>mybatis-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>
</project>
阅读全文

Maven,工厂模式,反射模式,Lambda表达式,stream流式处理

2023/10/26

Maven构建工具

Maven介绍
  • Maven是项目管理工具,对软件项目提供构建与依赖管理
  • Maven是Apache下的Java开源项目
  • Maven为Java项目提供了统一的管理方式,已经成为业界标准
Maven核心特性
  • 项目设置遵循统一的规则,保证不同开发环境的兼容性
  • 强大的依赖管理,项目依赖组件自动下载、自动更新
  • 可扩展的插件机制,使用简单,功能丰富
Maven的坐标
  • GroupId:机构或者团体的英文,采用”逆向域名”形式书写
  • ArtifactId:项目名称,说明其用途,例如:cms、oa…
  • Version:版本号,一般采用”版本+单词”形式,例如:1.0.0.RELEASE

Maven项目标准结构

目录 用途
${basedir} 根目录,用于保存pom.xml
${basedir}/src/main/java Java源代码目录
${basedir}/src/main/resources 资源目录,保存配置文件、静态图片等
${basedir}/src/test/java 测试类的源代码
${basedir}/src/test/resources 测试时需要使用的资源文件
${basedir}/target 项目输出的目录,用于存储jar、war文件
${basedir}/target/class 字节码(.class)的编译输出目录
${basedir}/pom.xml 项目(Project)对象(Object)模型(Model)文件

Maven依赖管理

  • Maven利用dependency(依赖) 自动下载、管理第三方Jar
  • 在pom.xml文件中配置项目依赖的第三方组件
  • maven自动将依赖从远程仓库下载至本地仓库,并在工程中引用
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>

Maven Central Repository Search可以搜索maven

pom.xml
<dependencies>
    <dependency>
          <groupId>com.belerweb</groupId>
          <artifactId>pinyin4j</artifactId>
          <version>2.5.1</version>
    </dependency>
</dependencies>
PinyinTestor.java
import net.sourceforge.pinyin4j.PinyinHelper;

import java.util.Scanner;

public class PinyinTestor {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.nextLine();
        String[] pingyin = PinyinHelper.toHanyuPinyinStringArray(str.charAt(0)); //将输入的第一个数据变成字符串数组
        for (String py : pingyin){
            System.out.println(py);
        }
    }
}

本地仓库与中央仓库

maven 在项目启动的时候会对 pom.xml 进行加载 之后会在本地仓库 .m2\repository 去查找依赖文件(jar包) 如果查不到或不存在 就会去中央仓库下载(repo.maven.apache.org)到本地仓库

项目打包

  • Maven可将Java项目打包为jar、war
  • Maven项目打包是通过Plugins(插件)技术实现
  • Maven输出jar包插件:maven-assembly-plugin
//阿里云镜像下载地址
<repositories>
  <repository>
      <!-- 创建私服的地址 -->
    <id>aliyun</id>
    <name>aliyun</name>
    <url>https://maven.aliyun.com/repository/public</url>
  </repository>
</repositories>

Maven构建Web工程

创建Maven-Project

Group Id: maven-first
Artifacr Id: maven
Version: 1.0.0-RELEASE
Packaging: jar

IntelliJ IDEA创建maven web项目(IDEA新手适用)_idea maven创建web项目-CSDN博客
IDEA2022版本创建maven web项目(两种方式)最全图文教学_idea创建maven项目没有webapp-CSDN博客
IDEA中创建Maven Web项目的两种方法_idea maven创建web项目-CSDN博客
Project Structure → Modules → +增加Web

web application exploded:这个是以文件夹形式发布项目,发布项目时就会自动生成文件夹在指定的output directory;
web application archive:就是war包形式,将项目打成一个war包在指定位置

Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)_tomcat 404_ISAS的博客-CSDN博客
Windows下如何查看某个端口被谁占用 | 菜鸟教程 (runoob.com)
(.iml的问题)IDEA中用Maven创建web项目部署运行时页面报错404解决方法_maven web项目404-CSDN博客

Web应用打包

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Web_one</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar\war</packaging>
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
            </plugin>
        </plugins>
    </build>

</project>

打包成war\jar后可以通过tomcat中的临时启动服务器来运行

Maven常用命令

命令 用途
mvn archetype:generate 创建Maven工程结构
mvn compile 编译源代码 .class
mvn test 执行测试用例
mvn clean 清除产生的项目
mvn package 项目打包
mvn install 安装至本地仓库

修改本地仓库地址

工厂模式 [运用于真实项目]

设计模式
  • 设计模式是前辈总结的设计经验
  • 设计模式的目标是代码更容易理解,更容易维护
  • 通过设计模式可以让代码更加可靠
设计模式的分类
  • 创建型模式 [帮助我们如何更精巧的创建对象]
  • 结构型模式 [在软件结构上通过重构\抽象 让软件结构变得更有条理]
  • 行为型模式 [现实中的场景对软件的设计和优化]
工厂模式
  • 工厂模式用于隐藏创建对象的细节
  • 工厂模式核心:工厂类(Factory)
  • 工厂模式可以细分为简单工厂、工厂方法与抽象方法

简单工厂图

项目应用-i18n国际化 (软件分工更明确 软件耦合降低)

不同国家显示页面的语言不同
抽象一个接口!!

反射

反射Reflect
  • 反射式在运行时动态访问类与对象的技术 [写死的代码不灵活]
  • 反射是JDK1.2版本后的高级特性,隶属于java.lang.reflect
  • 大多数Java框架都是基于反射实现参数配置、动态注入等特性
初始反射技术
未运用反射技术
 public static void case1(){
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入计算类名:");
        String op = scanner.next();
        System.out.print("请输入a:");
        int a = scanner.nextInt();
        System.out.print("请输入b:");
        int b = scanner.nextInt();
        MathOperation mathOperation = null;
        if(op.equals("Addition")){
            mathOperation = new Addition();
        }else if(op.equals("Subtraction")) {
            mathOperation = new Subtraction();
        }else if(op.equals("Multiplication")){
            mathOperation = new Multiplication();
        }else{
            System.out.println("无效的计算类");
            return;
        }
        float result = mathOperation.operate(a, b);
        System.out.println(result);
    }

======================================================
运用反射技术
 public static void case2(){
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入计算类名:");
        String op = scanner.next(); ****
        System.out.print("请输入a:");
        int a = scanner.nextInt();
        System.out.print("请输入b:");
        int b = scanner.nextInt();
        MathOperation mathOperation = null;
        try {
            mathOperation = (MathOperation) Class.forName("com.imooc.reflect." + op).newInstance(); //class.forName加载指定的类 实例化对象 运行时动态决定op创建哪些对象 访问哪些属性****
        }catch(Exception e){
            System.out.println("无效的计算类");
            return;
        }
        float result = mathOperation.operate(a, b);
        System.out.println(result); 
    }

反射的核心类

Class类

Class是JVM中代表”类和接口”的类
Class对象具体包含了某个特定类的结构信息
通过Class对象可获取对应类的构造方法/方法/成员变量

Class核心方法
方法 用途
Class.forName() [传入完整类名包括包] 静态方法,用于获取指定Class对象
classObj.newInstance() 通过默认构造方法创建新的对象
classObj.getConstructor() 获得指定的public修饰构造方法Constructor对象
classObj.getMethod() 获取指定的public修饰方法Method对象
classObj.getField() 获取指定的public修饰成员变量Field对象
Employee.java
package com.imooc.reflect.entity;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }
}
ClassSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

public class ClassSample {
    public static void main(String[] args) {
        try {
            //Class.forName()方法将指定的类加载到jvm,并返回对应Class对象
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            System.out.println("Employee已被加载到jvm");
            //newInstance通过默认构造方法创建新的对象
            Employee emp = (Employee)employeeClass.newInstance();
            System.out.println(emp);
        } catch (ClassNotFoundException e) {
            //类名与类路径书写错误时抛出"类无法找到"异常
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            //对象无法被实例化,抛出"实例化异常"
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            //非法访问异常 在程序外或作用域外访问对象或成员变量时抛出
            throw new RuntimeException(e);
        }
    }
Constructor构造方法类
  • Constructor类是对Java类中的构造方法的抽象
  • Contructor对象包括了具体类的某个具体构造方法的声明
  • 通过Constructor对象调用带参构造方法创建对象
方法 用途
classObj.getConstructor() 获取指定public修饰的构造方法对象
constructorObj.newInstance() 通过对应的构造方法创建对象
Employee.java
package com.imooc.reflect.entity;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Employee(Integer eno, String ename, Float salary, String dname) {
        this.eno = eno;
        this.ename = ename;
        this.salary = salary;
        this.dname = dname;
        System.out.println("Employee带参构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                '}';
    }
}
ConstructorSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ConstructorSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{ //得到对应的class对象
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            System.out.println(employee);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
            //类名与类路径书写错误时抛出"类无法找到"异常
        } catch (NoSuchMethodException e) {
            //没有找到与之对应格式的写法
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            //当被调用的方法的内部抛出了异常而没有被捕获时
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            //对象无法被实例化,抛出"实例化异常"
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            //非法访问异常 在程序外或作用域外访问对象或成员变量时抛出
            throw new RuntimeException(e);
        }
    }
}
Method类
  • Method对象指代某个类中的方法的描述
  • Method对象使用classObj.getMethod()方法获取
  • 通过Method对象调用指定对象的对应方法

Method核心方法

方法 用途
classObj.getMethod() 获取指定public修饰的方法对象
methodObj.invoke() 调用指定对象的对应方法
Employee.java
package com.imooc.reflect.entity;

import com.sun.org.apache.bcel.internal.generic.RETURN;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Employee(Integer eno, String ename, Float salary, String dname) {
        this.eno = eno;
        this.ename = ename;
        this.salary = salary;
        this.dname = dname;
        System.out.println("Employee带参构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                '}';
    }

    public Employee updateSalary(Float val){
        this.salary = this.salary + val;
        System.out.println(this.ename + "调薪至" + this.salary + "元");
        return this;

    }
}
MethodSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            Method updateSalaryMethod = employeeClass.getMethod("updateSalary", new Class[]{ //传入参数
               Float.class
            });
            Employee employee1 = (Employee)updateSalaryMethod.invoke(employee, new Object[]{1000f}); //若有返回值 要强制转换
            System.out.println(employee1);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
Field成员变量类
  • Field对应某个具体类中的成员变量的声明
  • Field对象使用**classObj.getField()**方法获取
  • 通过Field对象可为某对象成员变量赋值/取值

Field类核心方法

方法 用途
classObj.getField() 获取指定publicc修饰的成员变量对象
fieldObj.set() 为某对象指定成员变量赋值
fieldObj.get() 获取某对象指定成员变量数值

快速添加包裹try catch → 框上要包裹的 点Code → surround with
get()/set()都是(在entify中)public共有方法

Employee.java
package com.imooc.reflect.entity;

import com.sun.org.apache.bcel.internal.generic.RETURN;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    public String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Employee(Integer eno, String ename, Float salary, String dname) {
        this.eno = eno;
        this.ename = ename;
        this.salary = salary;
        this.dname = dname;
        System.out.println("Employee带参构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                '}';
    }

    public Employee updateSalary(Float val){
        this.salary = this.salary + val;
        System.out.println(this.ename + "调薪至" + this.salary + "元");
        return this;

    }
}
FieldSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class FieldSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            Field enameField = employeeClass.getField("ename");
            enameField.set(employee,"李雷");
            String ename = (String) enameField.get(employee);
            System.out.println("ename:" + ename);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}

getDeclared系列方法

  • getDeclaredConstructor(s) | Method(s) | Field(s) 获取对应对象
  • getConstructor(s) | Method(s) | Field(s) 只能获取public对象
  • 访问非作用域内构造方法、方法、成员变量,会抛出异常

public可以直接获取 private只能通过get…获取

Employee.java + getDeclaredSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class getDeclaredSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            Field[] fields = employeeClass.getDeclaredFields();
            for (Field field : fields){
//                System.out.println(field.getName());
                if (field.getModifiers() == 1) {//成员变量修饰符 public修饰
                    Object val = field.get(employee);
                    System.out.println(field.getName() + ":" + val);
                } else if (field.getModifiers() == 2) { //private修饰
                    String methodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//(0,1)是截取字符串 第一个大写字母
                    Method getMethod = employeeClass.getMethod(methodName);
                    Object ret = getMethod.invoke(employee);
                    System.out.println(field.getName() + ":" + ret);
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}

反射在项目中的应用

反射最重要的就是可以在运行时

反射的应用网站

对原始程序无需任何调整,只需要把对应的接口进行实现,放到线上服务器,再调整配置文件。

Zhcn.java
package com.imooc.i18n;

public class Zhcn implements I18N{
    @Override
    public String say() {
        return "生命不息奋斗不止";
    }
}
=========================================================
En.java
package com.imooc.i18n;

public class En implements I18N{
    @Override
    public String say() {
        return "Case to the struggle and cease to the life";
    }
}
========================================================
接口I18N.java
package com.imooc.i18n;

public interface I18N {
    public String say();
}
package com.imooc.i18n;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Properties;

public class Application {
    public static void say(){
        Properties properties = new Properties(); //加载指定的配置文件
        String configPath = Application.class.getResource("/config.properties").getPath();
        try {
            configPath = new URLDecoder().decode(configPath,"UTF-8");//路径中的空格默认得到url编码所以要转换一下
            properties.load(new FileInputStream(configPath)); //内容来源于文件 文件输入流
            String language = properties.getProperty("language");
            I18N i18n = (I18N)Class.forName(language).newInstance();
            System.out.println(i18n.say());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Application.say();
    }
}
language=com.imooc.i18n.En
或
language=com.imooc.i18n.Zhcn

Lambda表达式

  • JDK8(1.8以上)开始支持Lambda表达式,用来让程序编写更优雅
  • 利用Lambda可以更简洁的实现匿名内部类函数声明与调用
  • 基于Lambda提供stream流式处理极大简化对集合的操作
传统代码
List<String> names = Arrays.asList("peter","anna","mike","xenia");
//实现集合排序
Collections.sort(names,new Comparator<String>(){
  @Override
  public int compare(String a,String b){
      return b.compareTo(a);
  }  
});
使用Lambda表达式
List<String> names = Arrays.asList("peter","anna","mike","xenia");
//通过lambda表达式简化匿名类的编写
Collections.sort(names,(a,b) -> b.compareTo(a));

Lambda表达式语法 [代码脚手架]

(参数列表) —>实现语句
[使用逗号分割参数,参数类型可省略,单参数括号可省略]
[单行直接写 多行用{}包括]

MathOperation.java
public interface MathOperation {
    //四则运算接口
    public Float operate(Integer a, Integer b);
}
LambdaSample.java
public class LambdaSample {
    public static void main(String[] args) {
        //标准Lambda使用方法
        //约束条件:Lambda表达式只能实现有且只有一个抽象方法的接口,Java称为"函数式接口"
        //1.标注使用方式
        MathOperation addition = (Integer a, Integer b) -> {
            System.out.println("加法运算");
            return a+b+0f; //定义的接口是Float
        };
        System.out.println(addition.operate(5, 5));

        //2.lambda允许忽略参数类型
        MathOperation substration = (a,b) -> {
            return a-b+0f;
        };
        System.out.println(substration.operate(5, 3));

        //3.单行实现代码可以省略大括号和return
        MathOperation multiplication = (a,b) -> a*b+0f;
        System.out.println(multiplication.operate(3, 5));
    }
}

函数式编程

  • 函数式编程是基于函数式接口并使用lambda表达的编程方式
  • 函数式编程理念是将代码作为可重用数据带入到程序运行中
  • 函数式编程强调”你想做什么“,而不是”你想怎么做

函数式接口

  • 函数式接口是有且只有一个抽象方法的接口
  • Java中拥有大量函数式接口,如java.lang.Runnable
  • JDK8后提供了一系列新的函数式接口,位于java.util.function

函数式接口Perdicate

  • Perdicate是新增的函数式接口,位于java.util.function
  • Perdicate用于测试传入的数据是否满足判断要求
  • Perdicate接口需要实现test()方法进行逻辑判断

lambda表达式来实现predicate的验证
将已有的代码变成可重复使用的资源放入程序中

PredicateSample.java
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/*
    理解函数式编程
    Perdicate函数式接口的使用方法
 */
public class PredicateSample {
    public static void main(String[] args) {
        Predicate <Integer> predicate = n->n>4; //隐藏着return
        boolean result = predicate.test(10);
        System.out.println(result);
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        filter(list,n->n%2==1); //传入函数式接口的实现lambda 取所有奇数
        filter(list,n->n%2==0); //取所有偶数
        filter(list,n->n>5 && n%2==0); //取所有大于5的偶数
    }
    public static void filter(List<Integer> list, Predicate<Integer> predicate){
        for (Integer num:list){
            if (predicate.test(num)){
                System.out.println(num + " ");
            }
        }
    }
}

各种函数接口

JDK8常用函数式接口
consumer函数接口
接口 用途
Consumer< T > 对应有一个输入参数无输出的功能代码
Function< T,R > 对应有一个输入参数且需要返回参数的功能代码
Predicate< T > 用于条件判断,固定返回布尔值
ConsumerSample.java
import java.util.function.Consumer;

/*
    Consumer接口的使用
 */
public class ConsumerSample {
    public static void main(String[] args) {
        output(s-> System.out.println("向控制台打印:" + s));
        //字符串作为网络数据包向某个网站发送
        output(s->{
            System.out.println("向XXX网络发送数据包:" + s);
        });
    }
    public static void output(Consumer<String> consumer){
        String text = "苦其心志,劳其筋骨";
        consumer.accept(text);
    }
}
Function接口
FunctionSample.java
import java.util.Random;
import java.util.function.Function;

/*
    利用Function函数式接口生成定长随机字符串[加密解密会用到]
 */
public class FunctionSample {
    public static void main(String[] args) {
        Function<Integer,String> randomStringFunction = l->{
            String chars = "abcdefghijklmnopqrstuvxwyz0123456789";
            StringBuffer stringBuffer = new StringBuffer();
            Random random = new Random();
            for (int i = 0; i < l; i++) {
                int position = random.nextInt(chars.length());
                stringBuffer.append(chars.charAt(position));//按指定位置将字符提取并追加
            }
            return stringBuffer.toString();
        };
        String randowmString = randomStringFunction.apply(16);//生成16位长的字符串
        System.out.println(randowmString);
    }
}
@functionalInterface注解
MathOperation.java
@FunctionalInterface //通知编译器这是函数式接口,进行抽象方法检查
public interface MathOperation {
    //四则运算接口

    public Float operate(Integer a, Integer b);
}

函数式编程与面向对象编程比较

面向对象编程 函数式编程
设计思路 面向对象 面向过程
开发侧重 侧重过程,重分析,重设计 侧重结果,快速实现
可读性 结构复杂,相对较差 更适合人眼阅读,可读性更好
代码量
并发问题 设计不当,会出现线程安全问题 不会出现线程安全问题
健壮性
使用场景 中大型项目,多人协作工程 小型应用,要求快速实现

Stream流式处理

  • Stream流式处理式建立在Lambda基础上的多数据处理技术
  • Stream对集合数据处理进行高度抽象,极大简化代码量
  • Stream可对集合进行迭代,去重,筛选,排序,聚合等一系列处理

Stream示例

//获取List集合中最大的偶数
Optional<Integer> op = Arrays.asList(1,2,3,4,5,6).stream()
.filter(x->x%2==0) //处理完得到一个只包含偶数的list流数据
.sorted((a,b)->b-a) //大的在前面 小的在后面
.findFirst(); //获取最大的数据
System.out.println(op.get());

Stream常用方法

接口 用途
forEach 循环遍历
map map方法用于映射每个元素到对应的结果
filter filter方法用于通知设置的条件过滤出元素
limit limit方法用于获取指定数量的流
sorted sorted方法用于对流进行排序
Collectors Collectors类实现将流转换成集合和聚合元素
StreamGenerator.java
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/*
Stream流对象的五种创建方式
 */
public class StreamGenerator {
    //1.基于数组进行创建
    @Test
    public void generator1(){
        String[] arr = {"Lily","Andy","Jackson","Smith"};
        Stream<String> stream = Stream.of(arr);
        stream.forEach(s -> System.out.println(s)); //forEach中使用Lambda表达式
    }

    //2.基于集合进行创建
    @Test
    public void generator2(){
        List<String> list = new ArrayList<>();
        list.add("Lily");
        list.add("Andy");
        list.add("Jackson");
        list.add("Smith");
        Stream<String> stream = list.stream(); //利用集合获取stream
        stream.forEach(s -> System.out.println(s));
    }

    //3.利用generate方法创建无限长度流
    @Test
    public void generator3(){
        Stream<Integer> stream = Stream.generate(() -> new Random().nextInt(100000));//Supplier<T> s 创建新对象
        stream.limit(10).forEach(i -> System.out.println(i)); //limit限制长度
    }

    //4.基于迭代器创建流
    @Test
    public void generator4(){
        Stream<Integer> stream = Stream.iterate(1, n -> n + 1);//无限长度自增
        stream.limit(100).forEach(i -> System.out.println(i));
    }

    //5.基于字符序列创建流
    @Test
    public void genetator5(){
        String str = "abcdefg我";
        IntStream stream = str.chars();
        stream.forEach(c -> System.out.println((char)c));
    }
}
StreamMethod.java
import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamMethod {
    @Test //提取集合中所有偶数并求和
    public void case1(){
        List<String> list = Arrays.asList("1", "2", "3", "4", "5");
        int sum = list.stream() //获取stream对象
                .mapToInt(s -> Integer.parseInt(s)) //对每个元素字符串转为整数
                .filter(n -> n%2==0) //filter对流数据进行过滤
                .sum();//求和
        System.out.println(sum);
    }

    @Test //所有名字首字母大写
    public void case2(){
        List<String> list = Arrays.asList("lily","smith","jackson");
        List newList = list.stream()
                .map(s -> s.substring(0, 1).toUpperCase() + s.substring(1)) //首字母大写转换
//                .forEach(s -> System.out.println(s));
                .collect(Collectors.toList()); //collect对流数据进行收集,生成新的List/Set(将重复数据自动清除)
        System.out.println(newList);
    }

    @Test //将所有奇数从大到小进行排序,且不允许出现重复
    public void case3(){
        List<Integer> list = Arrays.asList(1, 60, 38, 21, 51, 60, 51, 73);
        List newList = list.stream().distinct() //去除重复的流数据
                .filter(n -> n%2==1)
                .sorted((a,b) -> b-a) //从大到小的数据排列
                .collect(Collectors.toList());
        System.out.println(newList);
    }
}
阅读全文

java数据库开发(JDBC)

2023/10/23

IDEA窗口快捷键

快捷键 描述
Ctrl+Alt+S Settings面板
Ctrl+Shift+F/R 当前Project中全局查找/替换
Ctrl+Shif+N 文件查找面板
Alt+Insert 快速生成面板
Ctrl+Shift+A Find Action模糊查询快速定位

代码快捷键

快捷键 描述
Ctrl+←→ 上一个/下一个单词
Ctrl+Shift+Enter 自动完成
Alt+Enter 智能提示
Ctrl+Alt+L 格式化代码
Ctrl+(Shift)+/ 行注释/块注释
Ctrl+Alt+Shift+J 列操作
Shift+F6 重命名(当前选择变量修改)
Ctrl+W 选中单词

代码快速定位

快捷键 描述
Ctrl+(Shift)+E 最近访问(编辑)的文件列表
Ctrl+Shift+1~9 创建书签
Shift+F11 查看书签
Ctrl+1~9 快速切换书签
Alt+←→ 切换书签
Template使用

Live Templates可以添加常用快捷字母作为快捷代码

右方 + “custom”
Abbreviation:al”
Description:Create ArrayList”
Template text:List< String >list = new ArrayList();”
Template text:List< $VAR1$>$VAR2$ = new ArrayList();”
点击define设置全部

运行与打包

快捷键 描述
Shift+F9 调试
Shift+F10 运行
F8 单步运行
F9 恢复运行至下一个端点
Shift+Ctrl+F8 查看所有端点
Jar包核心配置文件设置(加载响应的Class)

将编译的类导入jar包

‘……’ compile output
Project Structure → Artifacts → Outpub Layout

设置jar包的入口类 → Create Manifest… → Main Class(Jar包加载相应目录)

生成jar包

上述操作完毕后 点BuildBuild Artifacts…

IDEA快速开发Web应用

New Project → Java Enterprise → SDK1.8 = Java EE7

改变Tomcat启动时自动弹出的地址

Run/Debug Configurations → Deployment → 下方的 Application context

Project Structure

Artifacts 中默认存在 javaweb: war exploded 代表用文件夹的方式与Tomcat联动
点Add → Web Application: Archive打包 右侧是待添加 [文件成功发布到jar包中 ‘javaweb’ compile output] 右侧的Web facet resources 是 jsp, html等静态资源双击放到左边[‘javaweb’module: ‘Web’ facet resources] 至此javaweb.war包就包含了所有文件。上述操作完毕后 点BuildBuild Artifacts… build!!之后若在实际运行的时候放在D:\apache-tomcat-8.5.93\webapps内 之后启动tomcat[D:\apache-tomcat-8.5.93\bin\startup.bat]就可以显示结果

JDBC(Java DataBase Connectivity)快速入门

JDBC作用,在java程序中与关系型数据库进行交互

JDBC优点
  • 统一的API,提供一致的开发过程
  • 易于学习,容易上手,代码结构稳定
  • 功能强大,执行效率高,可处理海量数据

开发流程

1.加载并注册JDBC驱动
2.创建数据库连接
3.创建Satement对象
4.遍历查询结果
5.关闭连接.释放资源

Class.forName的作用

  • Class.forName用于加载指定的JDBC驱动类
  • Class.forName本质是通知JDBC注册这个驱动类
  • 驱动由数据库厂商自行开发,俩厂家不同链接祖父串了,字符串也不同

创建数据库连接代码

String dbDriver = "com.mysql.cj.jdbc.Driver"; //JDBC驱动类
String dbURL = "jdbc:mysql://localhost:3306/imooc"; //连接字符串
String dbUsername =  "root";
String dbPassword = "123456";
//1.加载并初始化JDBC驱动
Class.forName(dbDriver);
//2.创建数据库连接
Connection connection = DriverManager.getConnection(dbURL,dbUsername,dbPassword);
StandardJDBCSample.java
package com.example.imoocjdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class StandardJDBCSample {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            //1.加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver"); //加载指定的类
            //2.创建数据库连接
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&&allowPublicKeyRetrieval=true",
                    "root", "root"
            ); //程序和数据库的网络通信桥梁
            //3.创建Satement对象 ResultSet结果集
            Statement stmt = conn.createStatement(); //一条或多条sql语句
            ResultSet rs = stmt.executeQuery("SELECT * FROM employee WHERE dname='研发部'");
            //4.遍历查询结果
            while (rs.next()) {
                Integer eno = rs.getInt(1); //把当前行指定未知的提取 eno
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                if (conn != null && conn.isClosed() == false) {
                    //5.关闭连接.释放资源
                    conn.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

JDBC驱动的秘密

DriverManager
  • DriverManager用于注册/管理JDBC驱动程序
  • DriverManager.getConnection(连接字符串,用户名,密码)
  • 返回值Connection对象,对应数据库的物理网络连接
Connection对象
  • Connection对象用于JDBC与数据库的网络通信对象
  • java.sql.Connection是一个接口,具体由驱动厂商实现
  • 所有数据库的操作都建立在Connection上

MySQL连接字符串

  • 格式: jdbc:mysql://[主机ip] [:端口]/数据库名?参数列表
  • 主机ip与端口是可选设置,默认值为127.0.0.1与3306
  • 参数列表采用url编码,格式:参数1=值1&参数2=值2
MySQL连接字符串常用参数
参数名 建议参数值 说明
useSSL true(生产) false(开发) 是否禁用ssl
useUnicode true 启用unicode编码传输数据
characterEncoding UTF-8 使用UTF-8编码传输数据
serverTimezone Asia/Shanghai 使用东8时区时间,UTC+8
allowPublicKeyRetrieval true 允许从客户端获取公钥加密传输
超级异常捕获

选中所需要的代码区域 → Code → Surround With → 6.try…catch

SQL注入攻击 [数据泄露]

当输入部门名称:**’ or 1=1 or 1=’**
只要在or左右两侧有一个成立就都成立的 输入的数据中并没有对单引号加以处理;

package com.imooc.jdbc.hrapp.command;

import java.sql.*;
import java.util.Scanner;

/**
 * 数据查询方法
 */
public class QueryCommand implements Command {
    @Override
    public void execute() {
        System.out.print("请输入部门名称:");
        Scanner in = new Scanner(System.in);
        String pdname = in.nextLine();
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //1. 加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2. 创建数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
            //3. 创建Statement对象
            stmt = conn.createStatement();
            //结果集
            System.out.println("select * from employee where dname='" + pdname + "'");
            rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");
            //4. 遍历查询结果
            //rs.next()返回布尔值,代表是否存在下一条记录
            //如果有,返回true,同时结果集提取下一条记录
            //如果没有,返回false,循环就会停止
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5. 关闭连接,释放资源
            try {
                if(rs != null){
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(stmt != null){
                    stmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(conn != null && !conn.isClosed() ) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

PreparedStatement[解决SQL攻击注入问题(特殊字符转义)]

SQL注入攻击
  • SQL注入攻击是指利用SQL漏洞越权获取数据的黑客行为
  • SQL注入攻击根源是未对原始SQL中的敏感字符做特殊处理
  • 解决方法:放弃Statement改用PreparedStatement处理SQL
PreparedStatement [变化的地方用问号替代]
  • PreparedStatement预编译Statement是Statement的子接口
  • PreparedStatement对SQL进行参数化, 预防SQL注入攻击
  • PreparedStatement比Statement执行效率更高
  • 要用参数化的sql语句(问号只能出现在值的地方 且不能二次计算)
    String sql = “select * from employee where dname=? and eno > ?“;
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1,pdname); //插入多句 自动寻导入上方sql语句
    pstmt.setInt(2,3500);
//利用PreparedStatement预防SQL注入风险
//当dname值为' or 1=1 or 1=' 时,查询不到任何结果
//SQL:select * from employee where dname = '\' or 1=1 or 1=\”
String sql = "select * from employee where dname=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,dname); //设置SQL参数,参数从1开始
ResultSet rs = pstmt.executeQuery();
while(rs.next()){
    ...
}

JDBC实现写数据

封装DbUtils工具类 [重复代码封装工具类(封装打开和关闭连接方法)]
DbUtils.java
package common;

import kotlin.Result;

import java.sql.*;

public class DbUtils {
    /**
     * 创建新的数据库连接
     * @return 新的Connection对象
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        //1. 加载并注册JDBC驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2. 创建数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
        return conn;
    }

    /**
     * 关闭连接,释放资源
     * @param rs 结果集对象
     * @param stmt Statement对象
     * @param conn Connection对象
     */
    public static void closeConnection(ResultSet rs, Statement stmt, Connection conn){
        try {
            if(rs != null){
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(stmt != null){
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(conn != null && !conn.isClosed() ) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

JDBC执行INSERT语句

String sql = "insert into employee(eno,ename) values(?,?)";
PreparedStatement pstmt = conn.PreparedStatement(sql);
pstmt.setInt(1,10);
pstmt.setString(2,"张三");
//executeUpdate方法返回记录数
int cnt = pstmt.executeUpdate(); //cnt=1
System.out.println("数据新增成功");

JDBC执行UPDATE语句

String sql = "update employee set salary = salary + 1000 where dname=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"研发部");
//executeUpdate方法返回记录数
int cnt = pstmt.exexuteUpdate();
System.out.println(“研发部”+cnt+"名员工提薪1000元");

JDBC执行DELETE语句

String sql = "delete from employee where eno = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1,3395);
//executeUpdate方法返回记录数
int cnt = pstmt.executeUpdate();
System.out.println(cnt+"名员工数据已被删除");

JDBC事务管理方式

  • 事务是以一种可靠的、一致的方式,访问和操作数据库的程序单元
  • 说人话:要么把事情做完,要么什么都不做,不要做一半
  • 事务依赖于数据库实现,MySQL通过事务区作为数据缓冲地带
事务的提交操作

应用程序写操作給事务区等全部完成后事务区再commit提交給数据表一次性写入給mysql。提交成功后事务区中的数据被清空

事务的回滚操作

应用程序写操作給事务区,如果+100突然-100程序报错了,由jdbc会向事务区发起rollback回滚操作 清空事务区,最终数据表不会产生任何写操作[要么什么都不做,不要做一半]

JDBC两种事务模式

  • 自动提交事务模式

​ 自动提交模式是指每一次写操作SQL,自动提交事务

自动提交开启方法:
conn.setAutoCommit(true)

​ 自动事务是JDBC默认行为,此模式无法保证多数据一致性[A钱少了 B钱增加]

  • 手动提交事务模式

​ 手动提交模式是指显式调用commit()与rollback()方法管理事务

手动提交开启方法:
conn.setAutoCommit(false)

​ 手动提交事务可保证多数据一致性,但必须手动调用提交/回滚方法

实现批量增加员工

package test;

import common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TransactionSample {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        try { //JDBC默认使用自动提交模式
            conn = DbUtils.getConnection();
            conn.setAutoCommit(false); //关闭自动提交
            String sql = "insert ignore into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i = 1000; i < 2000; i++) {
                if (i==1005){
//                    throw new RuntimeException("插入失败");
                }
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1,i);
                pstmt.setString(2,"员工" + i);
                pstmt.setFloat(3,4000f);
                pstmt.setString(4,"市场部");
                pstmt.executeUpdate();
            }
            conn.commit(); //提交数据
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //回滚数据
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}

基于实体类实现分页数据封装

日常开发中如果要对数据进行提取以后最常见的形式是将数据转换为对应的实体类再放到集合中进行保存,即使被关闭数据也不会丢失。

package common;

import common.entity.Employee;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 分页查询员工数据
 */
public class PaginationCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入页号:");
        int page = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<Employee> list = new ArrayList(); //将底下的信息封装到实体类中
        try {
            conn = DbUtils.getConnection(); //?前面第几行 10从这行开始向后取10条记录 limit是分页独有的方言
            String sql = "select * from employee limit ?,10";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1,(page-1)*10); //第二页(2-1)*10从第十条记录开始向后取10条
            rs = pstmt.executeQuery();
            while(rs.next()){
                Integer eno = rs.getInt("eno"); //不能在原有字段1前再新增1 按照名字来获取
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                //JDBC获取日期使用java.sql.Date,其继承自java.util.Date
                //所以两者互相兼容
                Date hiredate = rs.getDate("hiredate");
                Employee emp = new Employee(); //每产生一条记录都要封装成Employee对象
                emp.setEno(eno);
                emp.setEname(ename);
                emp.setSalary(salary);
                emp.setDname(dname);
                emp.setHiredate(hiredate);//把每一条记录都封装成为了实体类 放入list
                list.add(emp);
            }
            System.out.println(list.size());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}

JDBC中Date日期对象的处理

package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/*
   新增员工数据
 */
public class InsertCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号:");
        int eno = in.nextInt();
        System.out.println("请输入员工姓名:");
        String ename = in.next();
        System.out.println("请输入员工薪资:");
        float salary = in.nextFloat();
        System.out.println("请输入隶属部门:");
        String dname = in.next();
        System.out.println("请输入入职日期:");
        String strHiredate = in.next();
        //String到java.sql.Date分为两步
        //1.前端传入的String字符串转为java.util.Date
        java.util.Date udHiredate = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            udHiredate = sdf.parse(strHiredate);//对原有字符串解析
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        //2.java.util.Date转为java.sql.Date
        long time = udHiredate.getTime();//获取从1970到现在的毫秒数
        java.sql.Date sdHiredate = new java.sql.Date(time); //sdHiredate成功表达了strHiredate所表达的时间
        Connection conn = null;
        PreparedStatement pstmt = null;
        //新增数据 获取数据库连接
        try {
            conn = DbUtils.getConnection(); //↓作为写操作的sql一定要是参数化的(pstmt)
            String sql = "insert into employee(eno,ename,salary,dname,hiredate) value(?,?,?,?,?)";
            pstmt = conn.prepareStatement(sql); //sql被解析
            pstmt.setInt(1,eno);
            pstmt.setString(2,ename);
            pstmt.setFloat(3,salary);
            pstmt.setString(4,dname);
            pstmt.setDate(5,sdHiredate); //目标java.sql.Date
            int cnt = pstmt.executeUpdate();//所有要改变数据表的都要使用executeUpdate
            System.out.println("cnt:" + cnt); //代表本次写入影响的记录数量
            System.out.println(ename + "员工入职手续已办理");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.closeConnection(null,pstmt,conn); //释放所有资源
        }
    }
}

JDBC批量处理

需要反复执行同时一次性要插入很多数据的操作使用批处理操作

BatchSample.java
package test;

import common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;

public class BatchSample {
    private static void tc1(){
        //标准未使用批处理
        Connection conn = null;
        PreparedStatement pstmt = null;
        try { //JDBC默认使用自动提交模式
            long startTime = new Date().getTime();
            conn = DbUtils.getConnection();
            conn.setAutoCommit(false); //关闭自动提交
            String sql = "insert ignore into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i = 100000; i < 200000; i++) {
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1,i);
                pstmt.setString(2,"员工" + i);
                pstmt.setFloat(3,4000f);
                pstmt.setString(4,"市场部");
                pstmt.executeUpdate();
            }
            conn.commit(); //提交数据
            long endTime = new Date().getTime();
            System.out.println("tc1()执行时长: " + (endTime-startTime));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //回滚数据
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }

    private static void tc2(){
        //使用批处理插入若干数据
        Connection conn = null;
        PreparedStatement pstmt = null;
        try { //JDBC默认使用自动提交模式
            long startTime = new Date().getTime();
            conn = DbUtils.getConnection();
            conn.setAutoCommit(false); //关闭自动提交
            String sql = "insert ignore into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i = 200000; i < 300000; i++) {
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1,i);
                pstmt.setString(2,"员工" + i);
                pstmt.setFloat(3,4000f);
                pstmt.setString(4,"市场部");
//                pstmt.executeUpdate();
                pstmt.addBatch();//将参数加入批处理任务
            }
            pstmt.executeBatch();//执行批处理任务
            conn.commit(); //提交数据
            long endTime = new Date().getTime();
            System.out.println("tc2()执行时长: " + (endTime-startTime));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //回滚数据
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
    public static void main(String[] args) {
        tc1(); //222279ms
        tc2(); //15827 ms
    }
}

综合数据库的增删改查

HumanResourceApplication.java
package test;

import common.*;

import java.util.Scanner;

public class HumanResourceApplication {
    public static void main(String[] args) {
        System.out.println("1-查询部门员工");
        System.out.println("2-办理员工入职");
        System.out.println("3-调整薪资");
        System.out.println("4-员工离职");
        System.out.println("5-分页查询员工数据");
        System.out.println("请选择功能:");
        Scanner in = new Scanner(System.in);
        Integer cmd = in.nextInt();
        Command command = null;
        switch (cmd){
            case 1://查询部门员工
                command = new PstmtQueryCommand();
                command.execute();
                break;
            case 2:
                command = new InsertCommand();
                command.execute();
                break;
            case 3:
                command = new UpdateCommand();
                command.execute();
                break;
            case 4:
                command = new DeleteCommand();
                command.execute();
                break;
            case 5:
                command = new PaginationCommand();
                command.execute();
                break;
        }
    }
}
插入员工
InsertCommand.java
package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/*
   新增员工数据
 */
public class InsertCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号:");
        int eno = in.nextInt();
        System.out.println("请输入员工姓名:");
        String ename = in.next();
        System.out.println("请输入员工薪资:");
        float salary = in.nextFloat();
        System.out.println("请输入隶属部门:");
        String dname = in.next();
        System.out.println("请输入入职日期:");
        String strHiredate = in.next();
        //String到java.sql.Date分为两步
        //1.前端传入的String字符串转为java.util.Date
        java.util.Date udHiredate = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            udHiredate = sdf.parse(strHiredate);//对原有字符串解析
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        //2.java.util.Date转为java.sql.Date
        long time = udHiredate.getTime();//获取从1970到现在的毫秒数
        java.sql.Date sdHiredate = new java.sql.Date(time); //sdHiredate成功表达了strHiredate所表达的时间
        Connection conn = null;
        PreparedStatement pstmt = null;
        //新增数据 获取数据库连接
        try {
            conn = DbUtils.getConnection(); //↓作为写操作的sql一定要是参数化的(pstmt)
            String sql = "insert into employee(eno,ename,salary,dname,hiredate) value(?,?,?,?,?)";
            pstmt = conn.prepareStatement(sql); //sql被解析
            pstmt.setInt(1,eno);
            pstmt.setString(2,ename);
            pstmt.setFloat(3,salary);
            pstmt.setString(4,dname);
            pstmt.setDate(5,sdHiredate); //目标java.sql.Date
            int cnt = pstmt.executeUpdate();//所有要改变数据表的都要使用executeUpdate
            System.out.println("cnt:" + cnt); //代表本次写入影响的记录数量
            System.out.println(ename + "员工入职手续已办理");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.closeConnection(null,pstmt,conn); //释放所有资源
        }
    }
}
更新员工数据
UpdateCommand.java
package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class UpdateCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int eno = in.nextInt();
        System.out.println("请输入员工新的薪资");
        float salary = in.nextFloat();
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "update employee set salary=? where dname=?";
             pstmt = conn.prepareStatement(sql);
             pstmt.setFloat(1,salary);
             pstmt.setInt(2,eno);
             int cnt = pstmt.executeUpdate();
             if (cnt == 1){
                 System.out.println("员工薪资调整完毕");
             }else {
                 System.out.println("未找到" + eno + "编号员工数据");
             }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}
删除员工数据
DeleteCommand.java
package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class DeleteCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int eno = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "delete from employee where eno = ?";
             pstmt = conn.prepareStatement(sql);
             pstmt.setFloat(1,eno);
             int cnt = pstmt.executeUpdate();
             if (cnt == 1){
                 System.out.println("员工离职手续已完成");
             }else {
                 System.out.println("未找到" + eno + "编号员工数据");
             }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}
查找员工数据
PstmtQueryCommand.java
package common;

import java.sql.*;
import java.util.Scanner;

/**
 * PreparedStatement对象使用方法
 */
public class PstmtQueryCommand implements Command {

    public void execute() {
        System.out.print("请输入部门名称:");
        Scanner in = new Scanner(System.in);
        String pdname = in.nextLine();
        Connection conn = null;
//        Statement stmt = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //1. 加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2. 创建数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
            //3. 创建PreparedStatement对象
            String sql = "select * from employee where dname=? and eno > ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1,pdname); //注意:参数索引从1
            pstmt.setInt(2,3500);
            //结果集
            rs = pstmt.executeQuery();
            //4. 遍历查询结果
            //rs.next()返回布尔值,代表是否存在下一条记录
            //如果有,返回true,同时结果集提取下一条记录
            //如果没有,返回false,循环就会停止
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5. 关闭连接,释放资源
            try {
                if(rs != null){
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(pstmt != null){
                    pstmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(conn != null && !conn.isClosed() ) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
分页查询员工数据
PaginationCommand.java
package common;

import common.entity.Employee;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 分页查询员工数据
 */
public class PaginationCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入页号:");
        int page = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<Employee> list = new ArrayList(); //将底下的信息封装到实体类中
        try {
            conn = DbUtils.getConnection(); //?前面第几行 10从这行开始向后取10条记录 limit是分页独有的方言
            String sql = "select * from employee limit ?,10";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1,(page-1)*10); //第二页(2-1)*10从第十条记录开始向后取10条
            rs = pstmt.executeQuery();
            while(rs.next()){
                Integer eno = rs.getInt("eno"); //不能在原有字段1前再新增1 按照名字来获取
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                //JDBC获取日期使用java.sql.Date,其继承自java.util.Date
                //所以两者互相兼容
                Date hiredate = rs.getDate("hiredate");
                Employee emp = new Employee(); //每产生一条记录都要封装成Employee对象
                emp.setEno(eno);
                emp.setEname(ename);
                emp.setSalary(salary);
                emp.setDname(dname);
                emp.setHiredate(hiredate);//把每一条记录都封装成为了实体类 放入list
                list.add(emp);
            }
            System.out.println(list.size());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}
DbUtils【通用】
DbUtils.java
package common;

import kotlin.Result;

import java.sql.*;

public class DbUtils {
    /**
     * 创建新的数据库连接
     * @return 新的Connection对象
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        //1. 加载并注册JDBC驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2. 创建数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
        return conn;
    }

    /**
     * 关闭连接,释放资源
     * @param rs 结果集对象
     * @param stmt Statement对象
     * @param conn Connection对象
     */
    public static void closeConnection(ResultSet rs, Statement stmt, Connection conn){
        try {
            if(rs != null){
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(stmt != null){
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(conn != null && !conn.isClosed() ) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
Employee(entity)
package common.entity;

import java.util.Date;

/**
 * 员工实体类
 */
public class Employee {
    /**
     * 1. 具备默认构造函数
     * 2. 属性私有
     * 3. 存在getter与setter
     */
    public Employee(){

    }
    //通常和数据库数据一一对应
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;
    private Date hiredate;

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }
}

阿里巴巴Druid连接池[类似于施工仓库 在启动应用时创建连接池]

JDBC先去创建与数据库的连接 比较浪费资源和时间

  • Druid是阿里巴巴开源连接池组件,是最好的连接池之一
  • Druid对数据库连接进行有效管理与重用,最大化程序执行效率
  • 连接池负责创建管理连接,程序只负责取用和归还

/druid-config.properties要放到resources文件夹内

druid-config.properties

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username=root
password=root
initialSize=10 #初始数量
maxActive=20 #数据库最大连接数量
#最好初始数量=最大数量 一开始j
DruidSample.java
package test;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import common.DbUtils;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

public class DruidSample {
    public static void main(String[] args) {
        //1.加载属性文件
        Properties properties = new Properties();
        String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
        //空格->%20 会被转换
        try {
            propertyFile = new URLDecoder().decode(propertyFile,"UTF-8"); //%20还原回去
            properties.load(new FileInputStream(propertyFile));
        } catch (Exception e) {
            e.printStackTrace();
        }

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //2.获取DataSource数据源对象
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            //3.创建数据库连接
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement("select * from employee limit 0,100");
            rs = pstmt.executeQuery();
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(eno + "-" + ename + "-" + salary + "-" + dname);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}

扩展知识:C3P0连接池

不用属性文件,改用c3p0-config.xml保存文件

在里面&无法转义 要写成 ‘ & amp;

idea C3P0时出现java.sql.SQLException: No suitable driver的几种解决办法

1、对lib包Add as library
2、c3p0-config.xml必须放在source目录下,在此目录会被自动读取
3、c3p0命名必须是c3p0-config.xml(至少xml格式是这样)
4、c3p0-config.xml文件配置错误、书写错误

c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/imooc?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <!-- 连接池初始连接数量 -->
        <property name="initialPoolSize">10</property>
        <!--最大连接数量-->
        <property name="maxPoolSize">20</property>
    </default-config>
</c3p0-config>
C3P0Sample.java
package test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import common.DbUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class C3P0Sample {
    public static void main(String[] args) {
        //1.加载配置文件
        //2.创建DataSource
        DataSource dataSource = new ComboPooledDataSource();
        //3.得到数据库连接
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement("select * from employee limit 0,10");
            rs = pstmt.executeQuery();
            while(rs.next()){
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(eno + "-" + ename + "-" + salary + "-" + dname);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(rs,pstmt,conn); //将数据库连接回收到连接池中而不是真正关闭
        }
    }
}

Apache Commos DBUtils

  • commons-dbutils是Apache提供的开源JDBC工具类库
  • 它是对JDBC的简单封装,学习成本极低
  • 使用commons-dbutils可以极大简化JDBC编码工作量
DbUtilsSample.java 【对数据的查询与更新】
package test;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import common.DbUtils;
import common.entity.Employee;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

/**
 * Apache DBUtils + Druid 联合使用演示
 */
public class DbUtilsSample {
    private static void query(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
        try {
            propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
            properties.load(new FileInputStream(propertyFile));
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            //利用Apache DbUtils大幅简化了数据的提取过程
            QueryRunner qr = new QueryRunner(dataSource); //查询执行者  ↓连接自动关闭 不用手动写代码
            List<Employee> list = qr.query("select * from employee limit ?,10",
                    new BeanListHandler<>(Employee.class),
                    new Object[]{10});//没有结果集只能new 结果自动转换成List实体类 后面的是问号赋值
            for (Employee emp : list){
                System.out.println(emp.getEname());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static void update(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
        Connection conn = null;
        try {
            propertyFile = new URLDecoder().decode(propertyFile,"UTF-8");
            properties.load(new FileInputStream(propertyFile)); //文件加载
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);//利用工厂类对properties信息进行载入创建对应的datasource对象
            conn = dataSource.getConnection();//获取数据库连接
            conn.setAutoCommit(false);
            String sql1 = "update employee set salary=salary+1000 where eno=?";
            String sql2 = "update employee set salary=salary-599 where eno=?";
            QueryRunner qr = new QueryRunner();
            qr.update(conn, sql1, new Object[]{1000});//写入表操作都用update
            qr.update(conn, sql2, new Object[]{1001});//分别完成加工资和减工资的操作
            conn.commit();//如果都执行成功 就提交 否则回滚
        } catch (Exception e) {
           e.printStackTrace();
            try {
                if (conn!=null && !conn.isClosed()){
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }finally {
            try {
                if (conn!=null && !conn.isClosed()){
                    conn.close(); //对数据库进行回收
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
//        query();
        update();
    }

}
阅读全文

思诚科技项目实训

2023/10/17

html基础知识

打开文件
拖拽法
菜单打开

拓展插件:Chinese、Open in browser

VSCode

代码格式化

  • Tab 往右缩进

  • Shift + Tab 向左回退

  • 一个Tab等于两个空格

  • 设置一个Tab等于2个空格

  • 设置键盘快捷方式 大写与小写 ctrl+shift+u/l

  • shift + alt + ↓ 快速复制上一行

  • ctrl + f 搜索

  • ctrl + h 替换

  • ctrl + z 撤销

  • 多光标修改 选中修改的 ctrl+d 加上上下移动

快速写代码 [按 ctrl+i 自动导入快捷代码]

  • div + Tab 快速输入div块

  • div.red + Tab < div class=”red”> < /div>

  • div#box.red + Tab < div id=”box” class=”red”> < /div>

  • div[name=box] [title=气泡] + Tab < div name=”box” title=”气泡”> < /div>

  • a#link.red[href=#] [ title=我是连接] + Tab < a href=”#” id=”link” class=”red” title=”我是连接”>< /a>

  • div{文本} + Tab < div>aaaa< /div>

  • 输入ul>li{项目} + Tab

    <ul>
      <li>牛逼</li>
    </ul>
    
  • ul#box>li.red[title=’”标题”]{项目1}

    <ul id="box">
      <li class="red" title="标题">项目1</li>
    </ul>
    
  • ui>li*5 一个ul标签和五个li标签 大于号是子类[父子关系]

  • p*3{段落$}

    <p>段落1</p>
    <p>段落2</p>
    <p>段落3</p>
    
  • ul#nav>li.item*5{项目列表$} + Tab

    <ul id="nav">
        <li class="item" 列表1=""></li>
        <li class="item" 列表2=""></li>
        <li class="item" 列表3=""></li>
        <li class="item" 列表4=""></li>
        <li class="item" 列表5=""></li>
    </ul>
    
  • div[name=”box”]>p.red>span*3{文本$} + Tab

    <div name="box">
        <p class="red">
            <span>文本1</span>
            <span>文本2</span>
            <span>文本3</span>
        </p>
    </div>
    <!--#是id    .是class    [name="box"]-->
    
  • h${标题$}*6

    <h1>标题1</h1>
    <h2>标题2</h2>
    <h3>标题3</h3>
    <h4>标题4</h4>
    <h5>标题5</h5>
    <h6>标题6</h6>
    
  • h${标题$}*6 按ctrl+i 自动导入快捷代码

  • (h2{标题}+p{段落})*3 [同类关系]

    <h2>标题</h2>
    <p>段落</p>
    <h2>标题</h2>
    <p>段落</p>
    <h2>标题</h2>
    <p>段落</p>
    
  • div#faq>h2{常见问题}dl.list>(dt{问题$}+dd{答案$})*4
    div#faq>(h2{常见问题}+dl.list>(dt{问题$}+dd{答案$})*4)
    
    <div id="faq">
        <h2>常见问题</h2>
        <dl class="list">
            <dt>问题1</dt>
            <dd>答案1</dd>
            <dt>问题2</dt>
            <dd>答案2</dd>
            <dt>问题3</dt>
            <dd>答案3</dd>
            <dt>问题4</dt>
            <dd>答案4</dd>
        </dl>
    </div>
    
  • 输入 ul>li{列表$$}*10 + Tab 确保位数

    <ul>
        <li>列表01</li>
        <li>列表02</li>
        <li>列表03</li>
        <li>列表04</li>
        <li>列表05</li>
        <li>列表06</li>
        <li>列表07</li>
        <li>列表08</li>
        <li>列表09</li>
        <li>列表10</li>
    </ul>
    

MarkDown语法

在VsCode预览ctrl + shift + v

中划线: ’ ~~ 1111 ~~
分割线:’— + 回车
超链接:’[网易] (http://www.163.com)
图片: ‘! [图片名称] (图片URL)

阅读全文

数据库

2023/10/8

数据库基础内容

数据库系统(DBMS)

  • 关系型数据库系统(RDBMS)是指使用了关系模型的数据库
  • 关系模型中,数据是分类存放的,数据之间可以有联系
  • 淘宝网背后是3000多个数据库并发的集群
  • DB2电信金融领域 Oracle数据库集群 MySQL开源灵活 SQL Server教育领域免费
  • NoSQL数据库[Redis]指的是数据分类存放,但是数据之间没有关联关系的数据库系统
    主流NoSQL数据库 => Redis(内存 双十一秒杀) MemCache MongoDB(新闻) Neo4J
    NoSQL数据库只是关系型数据库的补充

MySQL衍生版

Oracle Percona(Linux系统) MariaDB

重设root密码 (D:/temp.txt)

  • 创建一个Txt文件,定义修改密码的SQL语句

    ALTER USER 'root'@'localhost' IDENTIFIED BY '123456'; 
    
  • Windows PowerSheell(管理员) 窗口打开
    停止服务

    net stop mysql180
    
    mysqld --defaults-file="D:\MySQL\MySQL Server 8.0\my.ini" --init-file=="D:/temp.txt" --console
    ctrl+C取消
    

    启动服务

    net start mysql180
    

MySQL配置文件

  • my.ini文件中,我们可以设置各种MySQL的配置,例如字符集、端口号、目录地址等等

    my.init{客户端配置信息:[client]… [mysql]… 数据库配置信息:[mysqld]…}

[client]

# pipe=

# socket=MYSQL 端口号

port=3306

# 错误时主板没有轰鸣声
[mysql]
no-beep

# server_type=3
[mysqld]
#端口号
port=3306


# basedir="D:/MySQL/MySQL Server 8.0/"
# Path to the database root
datadir=D:/MySQL/MySQL Server 8.0\Data

# with an account. 密码认证插件
authentication_policy=mysql_native_password

#默认存储引擎
default-storage-engine=INNODB

# database servers. 开启严格模式
sql-mode="ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"

# General and Slow logging. 用文件记录日志
log-output=FILE
# 关闭日志输出
general-log=0
# 日志文件名称
general_log_file="PLUMINARY.log"
# 开启慢查询日志
slow-query-log=1
#慢查询日志文件名称
slow_query_log_file="PLUMINARY-slow.log"
#大于多少秒的执行SQL被记录在慢查询日志
long_query_time=10

# Error Logging.错误日志名称
log-error="PLUMINARY.err"

# from every other ID in use by any other source or replica. 集群会用到数据库ID
server-id=1

# 把表名转换成小写
lower_case_table_names=1

# 导入导出数据的目录地址
secure-file-priv="D:/MySQL/MySQL Server 8.0/Uploads"

#最大连接数
max_connections=151

定义逻辑库、数据表

  • SQL是用于访问和处理数据的标准的计算机语言
SQL语句的注释
# 这是一段注释文字
/* 这是另一段注释文字 */

创建逻辑库

CREATE DATABASE 逻辑库名称; #创建
SHOW DATABASES; #展现逻辑库
DROP DATABASE 逻辑库名称; #删除

创建数据表

CREATE TABLE 数据表(
  列名1 数据类型[约束] [COMMENT 注释],
  列名2 数据类型[约束] [COMMENT 注释],
  ......
)[COMMENT = 注释];


CREATA TABLE student(
  id INT UNSIGNED PRIMARY KEY, #主键约束 不可重复
  name VARCHAR(20) NOT NULL, #varchar 字符串 最大不超过20个字符 NOT NULL必填,不允许没有数据
  sex CHAR(1) NOT NULL, #char 字符
  birthday DATE NOT NULL, 
  tel CHAR(11) NOT NULL,
  remark VARCHAR(200)  #备注不超过200字符串
);

INSERT INTO student VALUES(1,"李强","男","1995-05-15","13312345678",NULL);

SHOW tables; #展现数据表名称
DESC student; #数据表结构具体情况
SHOW CREATE TABLE student; #查询当时的sql语句
DROP TABLE student; #删除数据表

数据定义语言:数据类型

数字

类型 大小 说明
TINYINT 1字节 小整数
SMALLINT 2字节 普通整数
MEDIUMIINT 3字节 普通整数
INT 4字节 较大整数
BIGINT 8字节 大整数
FLOAT 4字节 单精度浮点数
DOUBLE 8字节 双精度浮点数
DECIMAL[精确钱] ——– DECIMAL(10,2)
  • 十进制的浮点数无法在计算机中用二进制精确表达 比如0.2
   num FLOAT(20,10) #位数20 小数点后精确10位  在num中输入0.2的时候 变成了0.200000000030
=> num DECIMAL(20,10)

数据类型:字符串

类型 大小 说明
CHAR 1-255字符 固定长度字符串
VARCHAR 1-65535字符 不固定长度字符串
TEXT 1-65535字符 不确定长度字符串[后不加括号]
MEDIUMETEXT 1-1千6百万字符 不确定长度字符串[后不加括号]
LONGTEXT 1-42亿字符 不确定长度字符串[后不加括号]

数据类型:日期类型(年月日中间横线分割 要加引号)

类型 大小 说明
DATE 3字节 日期
TIME 3字节 时间
YEAR 1字节 年份
DATETIME 8字节 日期时间[电影开始时间]
TIMESTAMP 4字节 时间戳

修改表结构

添加字段
ALTER TABLE 表名称
ADD 列1 数据类型 [约束] [COMMENT 注释],
ADD 列2 数据类型 [约束] [COMMENT 注释],
......;

ADD address VARCHAR(200) NOT NULL;
修改字段名称
ALTER TABLE 表名称
CHANGE 列1 新列名1 数据类型 [约束] [COMMENT 注释],
CHANGE 列2 新列名2 数据类型 [约束] [COMMENT 注释],
......;

修改字段
ALTER TABLE student
MODIFY home_tel VARCHAR(20) NOT NULL;
CHANGE address home_address VARCHAR(200) NOT NULL; #改变字段名
删除字段
ALTER TABLE 表名称
DROP 列1,
DROP 列2,
......;

数据库的范式

第一范式:原子性

  • 第一范式是数据库的基本要求,不满足组这一点就不是关系数据库
  • 数据库的每一列都是不可分割的基本数据项,同一列中不能有多个值,也不能存在重复的属性
不符合第一范式
学号 姓名 班级
1000 刘娜 高三年级1班
符合第一范式
学号 姓名 年纪 班级
1000 刘娜 高三 1班

第二范式:唯一性

  • 数据表中的每条记录必须是唯一的。为了实现区分,通常要为表加上一列用来存储唯一标识,这个唯一属性列被称为主键列
无法区分重复的数据
学号 考试成绩 日期
230 58 2018-07-15
230 58 2018-07-15
数据具有唯一性
流水号 学号 考试成绩 日期
201807152687 230 58 2018-07-15
201807152694 230 58 2018-07-15

第三范式:关联性

  • 每列都与主键有直接关系,不存在传递依赖
违反第三范式
爸爸 儿子 女儿 女儿的玩具 女儿的衣服
陈华 陈浩 陈婷婷 海绵宝宝 校服

拿爸爸作为主键 儿子和女儿字段都是依赖于爸爸字段 但是后面的字段违反了第三范式 女儿的玩具和女儿的衣服是依赖于女儿这个字段的并不依赖于爸爸这个字段
数据表关系都非常松散 在检索的时候非常慢 因为爸爸主键字段查询非常快 但是查女儿的玩具和女儿的衣服找不到一样的主键

遵守第三范式
爸爸 儿子 女儿
陈华 陈浩 陈婷婷
女儿 女儿的玩具 女儿的衣服
陈婷婷 海绵宝宝 校服
  • 依照第三范式,数据可以拆分保存到不同的数据表,彼此保持关联
编号 部门 电话
10 财务部 1001
20 技术部 1002
30 销售部 1003
编号 姓名 性别 部门 入职日期
1 陈浩 10 2018-05-10
2 李婷婷 30 2018-03-22

部门字段也是依赖于编号的 两张表没有违反第三范式

字段约束

  • MySQL中的字段约束共有四种:
约束名称 关键字 描述
主键约束 PRIMARY KEY 字段值唯一,且不能为NULL
非空约束 NOT NULL 字段值不能为NULL
唯一约束 UNIQUE 字段值唯一,且可以为NULL
外键约束 FOREIGN KEY 保持关联数据的逻辑性

主键约束

  • 主键约束要求字段的值在全表必须唯一,而且不能为NULL值
  • 建议主键一定要使用数字类型,因为数字的检索速度会非常快
  • 如果主键是数字类型,还可也设置自动增长
CREATE TABLE t_teacher(
    id INT PRIMARY KEY AUTO_INCREMENT, #自带索引功能 自带排序
    ......
);

非空约束

  • 非空约束要求字段的值不能为NULL值
  • NULL值以为没有值,而不是 “” 空字符串
CREATE TABLE t_teacher(
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    married BOOLEAN NOT NULL DEFAULT FALSE #若不写则是默认值false
);

唯一约束

  • 唯一约束要求字段值如果不为NULL,那么在全表必须唯一
CREATE TABLE t_tracher(
    ......
    tel CHAR(11) NOT NULL UNIQUE
);
t_  tb_  一般是真实的表  
v_ vw_   一般是视图虚拟表的意思
总结代码
CREATE TABLE t_teacher(
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    tel CHAR(11) NOT NULL UNIQUE, #唯一约束 UNIQUE 字段值唯一,且可以为NULL
    married BOOLEAN NOT NULL DEFAULT FALSE
);
-------------------------------------------------------------------------
CREATE TABLE student(
    id INT UNSIGNED PRIMARY KEY,
    name VARCHAR(20) NOT NULL,
    sex CHAR(1) NOT NULL,
    birthday DATE NOT NULL,
    tel CHAR(11) NOT NULL,
    remark VARCHAR(200)
);
INSERT INTO student VALUES (1,"李强","男","1995-05-15","13312345678",NULL);
DESC student; 
SHOW CREATE TABLE student;

ALTER TABLE student
ADD address VARCHAR(200) NOT NULL, #添加字段信息
ADD home_tel CHAR(11) NOT NULL;

ALTER TABLE student
MODIFY home_tel VARCHAR(20) NOT NULL; #改字段类型信息

ALTER TABLE student
CHANGE address home_address VARCHAR(200) NOT NULL; #改变字段名

ALTER TABLE student
DROP address; #删除字段

外键约束

  • 外键约束用来保证关联数据的逻辑关系
  • 外键约束的定义是写在子表上的
编号 部门 电话
10 财务部 1001
20 技术部 1002
30 销售部 1003
编号 姓名 性别 部门 入职日期
1 陈浩 10 2018-05-10
2 李婷婷 30 2018-03-22
CREATE TABLE t_dept(
    deptno INT UNSIGNED PRIMARY KEY,
    dname VARCHAR(20) NOT NULL UNIQUE,
    tel CHAR(4) UNIQUE #写了电话必须唯一
);
CREATE TABLE t_emp(
    empno INT UNSIGNED PRIMARY KEY,
    ename VARCHAR(20) NOT NULL,
    sex EUNM("男","女") NOT NULL, #枚举 选择其中一个
    deptno INT UNSIGNED,
    hiredate DATE NOT NULL,
    FOREIGN KEY (deptno) REFERENCES t_dept(deptno) #和父表进行外键约束关联
);

不允许删除第一个编号 需要先删除第二个表的记录 再删除第一个表记录 逻辑关系有保证

外键约束的闭环问题 【因此开发中放弃外键约束】

  • 如果形成外键闭环,我们将无法删除任何一张表的记录

数据排序的好处

  • 一旦数据排序后,查找的速度就会翻倍,现实世界跟程序世界都是如此

如何创建索引

CREATE TABLE 表名称(
    ......,
    INDEX [索引名称] (字段),
    ......
);

#数据库对这个索引字段排序生成二叉树 每个字段都是有索引名称的
CREATE TABLE t_message(
    id INT UNSIGNED PRIMARY KEY,
    content VARCHAR(200) NOT NULL,
    type ENUM("公告","通报","个人通知") NOT NULL,
    create_time TIMESTAMP NOT NULL,
    INDEX idx_type (type)
); #利用二叉树的二分查找去查找索引字段就会非常快

如何添加与删除索引

CREATE INDEX 索引名称 ON 表名(字段); #添加索引
ALTER TABLE 表名称 ADD INDEX [索引名](字段); #添加索引
SHOW INDEX FORM 表名; #展示索引表  PRIMARY KEY AUTO_INCREMENT, #自带索引功能 自带排序
DROP INDEX 索引名称 ON 表名; #删除索引

索引的使用原则

  • 数据量很大,而且经常被查询的数据表可以设置索引 (日志表很少查询 无需设置 因为数据库要维护二叉树)
  • 索引只添加在经常被用作检索条件的字段上面
  • 不要在大字段上创建索引

(以上均为DDL语句)

DML语句

数据操作语句:普通查询

记录查询
  • 最基本的查询语句是由SELECTFROM关键字组成
USE demo;
SELECT * FROM t_emp; #FROM是从员工表查数据 *是在结果集里显示员工表所有字段
SELECT empno,ename,sal FROM t_emp; #不查询所有 单独查询想要的
  • SELECT语句屏蔽了物理层的操作,用户不必关心数据的真实存储,交给数据库高效查找数据
  • 通常情况下 ,SELECT子句中使用了表达式,那么这列的名字就默认为表达式,因此需要一种对列明重命名的机制 [起别名:只是对查询的结果集字段改名称]
SELECT
  empno,
  sal*12 AS "income"
FROM t_emp;
  • 上述sql 查询语句的子句执行顺序

词法分析与优化:读取SQL语句
FROM:选择数据来源
SELECT:选择输出内容

数据操作语言:数据分页

  • 朋友圈只会加载少量部分信息,不用一次性加载全部朋友圈,那样只会浪费CPU时间、内存和网络带宽
  • 如果结果集的记录很多,则可以使用LIMIT关键字限定结果集数量
SELECT ... FROM ... LIMIT 起始位置,偏移量; 
SELECT empno,ename FROM t_emp LIMIT 0,20; #从0往后取20条数据
数据分页简写
  • 如果LIMIT子句只有一个参数,它表示的是偏移量,起始值默认为0
SELECT empno,ename FROM t_emp LIMIT 10;
SELECT empno,ename FROM t_emp LIMIT 0,10;
FROM → SELECT → LIMIT

结果集排序

  • 如果没有设置,查询语句不会对结果集进行排序。也就是说,如果想让结果集按照某种顺序排序,就必须使用ORDER BY子句 【默认升序
SELECT ... FROM ... ORDER BY 列名 [ASC|DESC];
SELECT ename,sal FROM t_emp ORDER BY sal;

排序关键字

  • ASC代表升序(默认),DESC代表降序
  • 如果排序列是数字类型,数据库就按照数字大小排序,如果是日期类型就按照日期大小排序,如果是字符串就暗战字符集序号排序。
SELECT ename,sal FROM t_emp ORDER BY hiredate DESC;
排序字段内容相同的情况
  • 如果两条数据排序字段内容相同 sal 都是3000
    默认情况下是按照主键升序
多个排序字段
  • 使用ORDER BY规定首要排序顺序条件和次要排序条件。数据库会先按照要排序条件排序,如果遇到首要排序内容相同的记录,那么就会启动次要排序条件接着排序
SELECT ename,sal,hiredate 
FROM t_emp 
ORDER BY hiredate DESC,sal ASC;
#先按照首要排序hiredate降序 再启动次要排序sal降序

SELECT ename,sal,hiredate
FROM t_emp
ORDER BY sal DESC 
LIMIT 0,5  #工资排在前五位进行降序
排序+分页
  • ODER BY 子句书写的时候放在LIMIT子句的前面
    FROM → SELECT → ORDER BY → LIMIT

结果集中的重复数据

  • 假如我们要查询员工表有多种职业,写出来的sql语句

    SELECT job FROM t_emp; #结果集内可能会出现重复记录
    
  • 如果去除重复的数据,可以使用 DISTINCT 关键字来实现

    SELECT DISTINCT 字段 FROM ...;
    SELECT DISTINCT job FROM t_emp;
    
注意事项
  • 使用DISTINCTSELECT子句中只能查询一列数据,如果查询多列,去除重复记录就会失效

    SELECT DISTINCT job,ename FROM t_emp; #有job相同但是ename不相同 不能查询
    
  • DISTINCT关键字只能再SELECT子句中使用一次 [必须放在第一个字段前面]

数据操作语言:条件查询(一)

  • 满足某一种或几种条件的记录。这类条件要用WHERE子句来实现数据的筛选

    SELECT ... FROM ... WHERE 条件 [AND|OR] 条件 ...;
    
    SELECT empno,ename,sal FROM t_emp
    WHERE deptno=10 AND sal>=2000;
    
    SELECT empno,ename,sal
    FROM t_emp
    WHERE(deptno=10 OR deptno=20) AND sal>=2000;
    
四类运算符
  • WHERE语句中的条件运算会用到以下四种运算符

    序号 运算符
    1 数学运算符
    2 比较运算符
    3 逻辑运算符
    4 按位运算符
算数运算符 [+加 -减 *乘 /除 %模]

NULL值与任何数字加减乘除都是NULL值 如果想要运算 必须加入 IFNULL(null,0); 意思是遇到NULL值就用0来计算 10+IFNULL(null,0) = 10
DATEDIFF(入职日期-现在的日期)/365

从t_emp表中找出 号位是10 和 总工资≥15000 的并且 计算工龄超过20年的人
SELECT empno,ename,sal,hiredate
FROM t_emp
WHERE deptno=10 AND (sal+IFNULL(NULL,0))*12>=15000
AND DATEDIFF(NOW(),hiredate)/365>=20;
比较运算符 [>大于 >=大于等于 <小于 <=小于等于 =等于 !=不等于 IN包含deptno IN(10,30,40)]
查询10 20 30部门里面在1980年以前入职的员工而且不能是SALESMAN职位
SELECT
  empno,ename,sal,deptno,hiredate
FROM t_emp;
WHERE deptno IN(10,20,30) AND job!="SALESMAN"
AND hiredate<"1985-01-01";
+续比较运算符
序号 表达式 意义 例子
8 IS NULL 为空 comm IS NULL
9 IS NOT NULL 不为空 comm IS NOT NULL
10 BETWEEN AND 范围 sal BETWEEN 2000 AND 3000
11 LIKE 模糊查询 ename LIKE “A%”
12 REGEXP 正则表达式 ename REGEXP “[a-zA-Z]{4}”

__代表前方一个未知 %代表前方N个未知

SELECT 
ename,comm,sal
FROM t_emp WHERE comm IS NULL
AND sal BETWEEN 2000 AND 3000;
AND ename LIKE "_LAKE";

SELECT 
ename,comm,sal
FROM t_emp WHERE comm IS NULL
AND sal BETWEEN 2000 AND 3000;
AND ename REGEXP "^[\\u4e00-\\u9fa5]{2,4}$"; #正则表达汉字范围寻找两到四个中文字符
按位运算符 [&位与 |位或 ~位取反 ^位异或 <<左移 >>右移]

<< 左移 10<<1 把10转换成二进制位 在最右面补上一个0 整体向左移动了一个单位
》》左移 10<<1 把10转换成二进制位 在最右面抹去一个0 整体向右移动了一个单位

二进制按位运算

  • 二进制运算的实质是将参与运算的两个操作数,按对应的二进制数逐位进行逻辑运算
    SELECT 3 & 7; 0011 & 0111 = 0011 = 3

数据操作语言:条件查询(二) [AND与 OR或 NOT非 XOR异或]

查询10和20之外部门的信息
SELECT
  ename,deptno,sal
FROM t_emp
WHERE NOT deptno IN(10,20) XOR sal>=2000;

WHERE子句的注意事项

  • WHERE子句中,条件执行的顺序是从左到右的。所以我们应该把索引条件,或者筛选掉记录最多的条件写在最左侧
    SELECT empno,ename FROM t_emp
    WHERE ename = "FORD" AND sal >= 2000;
    
    SELECT empno,ename FROM t_emp
    WHERE deptno = 10 AND sal >= 2000;
    

各种子句的执行排序

FROM → WHERE → SELECT → ORDER BY → LIMIT

先表 再查出符合条件的记录 才能从中挑选出符合的字段 先排序后限制


数据库高级内容

数据操作语言:聚合函数

  • 聚合函数在数据的查询分析中,应用十分广泛。聚合函数可以对数据求和、求最大值最小值、求平均值等等

  • 求公司员工的平均月收入是多少?

    底薪+佣金(不是null)
    SELECT AVG(sal+IFNULL(comm,0)) FROM t_emp;
    
SUM函数
  • SUM函数用于求和,只能用于数字类型,字符类型的统计结果为0,日期类型统计结果是毫秒数相加

    SELECT SUM(ename) FROM t_emp
    
    SELECT SUM(sal) FROM t_emp
    WHERE deptno IN (10,20);
    
MAX函数
  • MAX函数用于获得非空值的最大值

    SELECT MAX(comm) FORM t_emp;
    
    ①查询10和20部门中,月收入最高的员工?
    SELECT 
    MAX(sal+IFNULL(comm,0)) FROM t_temp
    FROM t_emp
    WHERE deptno IN (10,20);
    
    ②查询员工名字最长的是几个字符?
    SELECT MAX(LENGTH(ename)) FROM t_emp;
    
    SELECT
    SUM(sal),MAX(sal+IFNULL(comm,0)) 
    FROM t_emp
    WHERE deptno IN(10,20); 
    
MIN函数
  • MIN函数用于获得非空值的最小值

    SELECT MIN(empno) FROM t_emp;
    
AVG函数
  • AVG函数用于获得非空值的平均值,非数字数据统计结果为0

    SELECT AVG(sal+IFNULL(comm,0)) FROM t_emp;
    
COUNT函数
  • COUNT(*) [找所有]用于获得包含空值的记录数,COUNT(列名)用于获得包含非空值的记录数

    SELECT COUNT(*) FROM t_emp; #统计所有   15 
    SELECT COUNT(comm) FROM t_emp; #统计数量是非空   5
    
  • 查询10和20部门中,底薪超过2000元并且工龄超过15年的员工人数

    SELECT COUNT(*) 
    FROM t_emp
    WHERE deptno IN(10,20) 
    AND sal>=2000
    AND DATEDIFF(NOW(),hiredate)/365>=15;
    
  • 查询1985年以后入职的员工,底薪超过公司平均底薪的员工数量?

    ×××××××××错误示范×××××××××
    SELECT COUNT(*) FROM t_emp
    WHERE hiredate>="1985-01-01"
    AND sal>AVG(sal); #AVG无法运行 聚合函数不能出现在WHERE里面
    

数据操作语言:分组查询

为什么要分组?
  • 默认情况下汇总函数是对全表范围内的数据做统计
  • GROUP BY子句的作用是通过一定的规则将一个数据集划分成若干个小的区域,然后针对每个小区域分别进行数据汇总处理
SELECT deptno,ROUND(AVG(sal))
FROM t_emp
GROUP BY deptno; #分组来计算AVG

逐级分组

  • 数据库支持多列分组条件,执行的时候逐级分组
  • 查询每个部门里,每种职位的人员数量和平均底薪
#按照部门和工作分组 ↓这种职位人数 ↓这种职位底薪平均值
SELECT deptno,job,COUNT(*),AVG(sal)
FROM t_emp 
GROUP BY deptno,job
ORDER BY deptno; #按照deptno去排序

对SELECT子句的要求

  • 查询语句中如果含有GROUP BY子句,那么SELECT子句中的内容就必须要遵守规定:SELECT子句中可以包括聚合函数,或者GROUP BY子句的分组列,其余内容均不可以出现在SELECT子句中
#正确示范
SELECT deptno,COUNT(*),AVG(sal)
FROM t_emp GROUP BY deptno;

#错误示范
SELECT deptno,COUNT(*),AVG(sal),sal
FROM t_emp GROUP BY deptno;

对分组结果集再次做汇总计算

SELECT 
deptno,COUNT(*),AVG(sal),MAX(sal),MIN(sal)
FROM t_emp 
GROUP BY deptno WITH ROLLUP; #WITH ROLLUP对汇总函数再次进行汇总运算

GROUP_CONCAT函数

  • GROUP_CONCAT函数可以把分组查询中的某个字段拼接成一个字符串

  • 查询每个部门内底薪超过2000元的人数和员工姓名

    SELECT deptno,GROUP_CONCAT(ename),COUNT(*)
    FROM t_emp 
    WHERE sal>=2000
    GROUP BY deptno;
    
    deptno COUNT(*) GROUP_CONCAT(ename)
    10 2 CLARK,KING
    20 3 JONES,SCOTT,FORD
    30 1 BLAKE

各种子句的执行顺序

FROM → WHERE → GROUP BY → SELECT → ORDER BY → LIMIT

WHERE符合的留下来交给GROUP BY去分组之后调用SELECT中的聚合函数计算 ORDER BY子句对结果排序交给LIMIT子句来挑选返回哪些数据

分组查询遇到的困难?

  • 查询部门平均底薪超过2000元的部门编号

    #错误演示[因为WHERE语句出现了聚合函数]
    SELECT deptno FROM t_emp
    WHERE AVG(sal)>=2000
    GROUP BY deptno;
    
  • HAVING语句是紧紧跟着GROUP BY语句的 HAVING子句可以写聚合函数作为判断条件

    SELECT deptno
    FROM t_emp
    GROUP BY deptno HAVING AVG(sal)>=2000;
    

HAVING子句的用途

  • 查询每个部门中,1982年以后入职的员工${普通条件可以写在WHERE里}$超过2个人${COUNT(*)>=2}$的部门编号
    不能拿聚合函数某一个字段做判断

    SELECT deptno 
    FROM t_emp
    WHERE hiredate>="1982-01-01"
    GROUP BY deptno HAVING COUNT(*)>=2;
    ORDER BY deptno ASC;
    
    SELECT deptno 
    FROM t_emp
    WHERE hiredate>="1982-01-01"
    GROUP BY deptno HAVING COUNT(*)>=2 AND AVG(sal)>=2000;
    

HAVING子句的特殊用法 [作用类似于WHERE]

  • 按照数字1分组,MySQL会根据SELECT子句中的列进行分组,HAVING子句也可以正常使用
    能用WHERE就不要先用HAVING 它的作用是給聚合函数做判断

    #不推荐写法
    SELECT deptno,COUNT(*) 
    FROM t_emp
    GROUP BY 1 HAVING deptno IN (10,20);
    
    #推荐写法
    SELECT deptno,COUNT(*) 
    FROM t_emp
    WHERE deptno IN(10,20)
    GROUP BY 1;
    

数据操作语言:表链接查询(一)

从多张表中提取数据
  • 从多张表中提取数据,必须指定关联的条件。如果不定义关联条件就会出现无条件链接,两张表的数据会交叉连接,产生笛卡尔积

  • 规定了链接条件的表链接语句,就不会出现笛卡尔积 [On条件]

    SELECT e.empno,e.ename,d.dname
    FROM t_emp e JOIN t_dept d #給表起别名
    ON e.deptno=d.deptno; #员工部门编号等于部门的部门编号
    

表链接的分类

  • 表链接分为两种:内链接外连接
  • 内链接是结果集中只保留符合连接条件的记录
  • 外连接是不管符不符合链接条件,记录都要保留在结果集中
内链接
SELECT ... FROM 表1
[INNER] JOIN 表2 ON 条件
[INNER] JOIN 表3 ON 条件
...
内连接的多种语法形式
SELECT ... FROM 表1 JOIN 表2 ON 连接条件;
SELECT ... FROM 表1 JOIN 表2 WHERE 连接条件
SELECT ... FROM 表1,表2 WHERE 连接条件;

内连接练习1

  • 查询每个员工的工号、姓名、部门名称、底薪、职位、工资等级?且保证工资符合范围
SELECT e.empno,e.ename,d.dname,e.sal,e.job,s.grade
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno
JOIN t_salgrade s ON e.sal BETWEEN s.losal AND s.hisal;
  • 内连接的数据表不一定必须有同名字段,只要字段之间符合逻辑关系就可以

内连接练习2

  • 查询与SCOTT相同部门的员工都有谁?

  • 相同的数据表也可以做表连接

    SELECT deptno
    FROM t_emp
    WHERE ename="SCOTT"; 
    
    #子查询的结果变成了条件  SCOTT本人不算
    SELECT deptno
    FROM t_emp
    WHERE deptno=(SELECT dptno FROM t_emp WHERE ename="SCOTT");
    AND ename!="SCOTT";
    
    #改造快速一些  ON后是筛选条件 和 WHERE作用差不多
    SELECT e2.ename
    FROM t_emp e1 JOIN t_emp e2 ON e1.deptno=e2.deptno
    WHERE e1.ename="SCOTT" AND e2.ename!="SCOTT"; 
    

数据操作语言:表链接查询(二)

内连接查询练习1
  • 查询底薪超过公司平均底薪的员工信息?
    把聚合函数查询的结果做成一张表 再进行表连接 【分部描述】
#错误展示
SELECT e2.empno,e2.ename,e2.sal  #因为ON可以换成WHERE 然而WHERE后面有聚合函数 所以会报错
FORM t_emp e1 JOIN t_emp e2 ON e2.sal>=AVG(e1.sal);

#把聚合函数查询的结果做成一张表 再进行表连接 【分部描述】
SELECT AVG(sal)
FROM t_emp;

SELECT e.empno,e.ename,e.sal
FROM t_emp e JOIN(SELECT AVG(sal) avg FROM t_emp) t #avg别名
ON e.sal>=t.avg;
  • 查询RESEARCH部门的人数、最高底薪、最低底薪、平均底薪、平均工龄?
SELECT COUNT(*),MAX(e.sal),MIN(e.sal),AVG(e.sal),AVG(DATEDIFF(NOW(),e.hiredate)/365)
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno
WHERE d.dname="RESEARCH";

SELECT FLOOR(28.9); #变成28 向上取整
SELECT CEIL(1.1);   #变成1  向上取整
内连接查询练习2
  • 查询每种职业的最高工资、最低工资、平均工资、最高工资等级和最低工资等级?

    SELECT 
    e.job,MAX(e.sal+IFNULL(e,comm,0)),
    MIN(e.sal+IFNULL(e,comm,0)),AVG(e.sal+IFNULL(e,comm,0)),
    MAX(s.grade),MIN(s.grade)
    FROM t_emp e JOIN t_salgrade s
    ON (e.sal+IFNULL(e,comm,0)) BETWEEN s.losal AND s.hisal
    GROUP BY e.job;
    
  • 查询每个底薪超过部门平均底薪的员工信息 [用表连接 而不是子查询]

    SELECT e.empno,e.ename,e.sal
    FROM t_emp e JOIN
    (SELECT deptno,AVG(sal) AS avg FROM t_emp GROUP BY dptno) t #别名t
    ON e.deptno=t.deptno AND e.sal>=t.avg;
    

数据操作语言:表链接查询(三)

为什么要使用外连接
  • 如果说陈浩是一名临时人员,没有固定的部门编制(NULL),那么我们想查询每名员工和他的部门名称,用内连接就会遗漏掉陈浩,所以要引用外连接的语法才能解决这个问题
外连接简介
  • 外连接与内连接的区别在于,除了符合条件的记录之外,结果集中还会保留不符合条件的记录

    SELECT e.empno,e.ename,d.dname
    FROM t_emp e  #因为陈浩部门编制是NULL 不能直接JOIN 只能LEFT JOIN
    LEFT JOIN t_dept d ON e.deptno=d.deptno; 
    
    7902 FORD RESEARCH
    7934 MILLER ACCOUNTING
    8000 陈浩 (NULL)

左连接和右连接

  • 左外连接就是保留左表所有的记录,与右表做连接。如果右表有符合条件的记录就与左表连接。如果右表没有符合条件的记录,就用NULL与左表连接。右外连接也是如此。

    SELECT e.empno,e.ename,d.dname
    FORM t_dept d RIGHT JOIN t_emp e #要保留所有的t_emp e
    ON e.deptno=d.deptno;
    

外连接练习1

  • 查询每个部门的名称和部门的人数? [40有部门没员工 要保存空值]
    左外连接把部门表写在左侧 右外连接把部门表写在右侧

    SELECT d.dname,COUNT(*)
    FROM t_dept d LEFT JOIN t_emp e #保留左表的所有记录 右表有空
    ON d.deptno=e.deptno
    GROUP BY d.deptno; #因为保留了左表所有记录 所以分组按照部门号分
    #最终有一条t_dept与t_emp中的NULL做连接 所以 COUNT算上此记录
    #若要将右表的NULL值忽略掉 要在COUNT(d.deptno)这样写
    
    SELECT d.dname,COUNT(d.deptno)
    FROM t_dept d LEFT JOIN t_emp e #保留左表的所有记录
    ON d.deptno=e.deptno
    GROUP BY d.deptno;
    
    dname COUNT(d.deptno)
    ACCOUNTING 3
    RESEARCH 5
    SALES 6
    OPERATIONS 0
  • 查询每个部门的名称和部门的人数?如果没有部门的员工,部门名称用NULL代替

  • UNION关键字可以将多个查询语句的结果集进行合并

    (查询语句) UNION (查询语句) UNION (查询语句)...
    (SELECT d.name,COUNT(e.deptno)
    FROM t_dept d LEFT JOIN t_emp e
    ON d.deptno=e.deptno
    GROUP BY d.deptno
    )UNION
    (SELECT d.dname,COUNT(*)
    FROM t_dept d RIGHT JOIN t_emp e
    ON d.deptno=e.deptno
    GROUP BY d.deptno
    );
    

数据库操作语言:表连接查询(四)

  • 查询每名员工的编号、姓名、部门、月薪、工资等级、工龄、上司编号、上司姓名、上司部门?
    [不知道:员工信息 员工的上司信息 两个不知道信息是不能使用子查询的 员工表 部门表 公司等级表]

    #陈浩要保存下来 用外连接
    SELECT
    e.empno,e.ename,d.dname,
    e.sal+IFNULL(e.comm,0),s.grade,
    FLOOR(DATEDIFF(NOW(),e.hiredate)/365),
    t.empno AS mgrno,t.ename AS mname,t.dname AS mdname #定义上司的数据
    FROM t_emp e 
    LEFT JOIN t_dept d ON e.deptno=d.deptno #结果集所有记录保存下来跟工资等级表做连接
    LEFT JOIN t_salgrade s ON e.sal BETWEEN s.losal AND s.hisal
    LEFT JOIN
    (SELECT e1.empno,e1.ename,d1.dname
    FROM t_emp e1 JOIN t_dept d1
    ON e1.deptno=d1.deptno
    )t ON e.mgr=t.empno;
    

外连接的注意事项

  • 内连接值保留符合条件的记录,所以查询条件写在ON子句和WHERE子句中的效果是相同的。但是外连接里,条件写在WHERE子句里,不符合条件的记录是会被过滤掉的,而不是保留下来的。

数据操作语言:子查询(一)

WHERE中的子查询是需要反复查询的 不推荐使用,但是把它所得的结果集作为一张表跟其他表做连接是推荐的

  • 子查询是一种查询中嵌套查询的语句

  • 查询底薪超过公司平均底薪的员工信息

    SELECT empno,ename,sal
    FORM t_emp
    WHERE sal>=(SELECT AVG(sal) FROM t_emp); #不推荐使用 最好用表连接
    
子查询的分类
  • 子查询可以写在三个地方:WHERE子句、FROM子句、SELECT子句,但是只有FROM子句子查询是最可取的
WHERE子查询
  • 这种子查询最简单,最容易理解,但是确实效率很低的子查询

  • 查询底薪超过公司平均底薪的员工信息

    SELECT empno,ename,sal
    FORM t_emp #↓↓↓↓ 比较每条记录都要重写执行子查询 ↓↓↓↓
    WHERE sal>=(SELECT AVG(sal) FROM t_emp); #不推荐使用 最好用表连接
    
FROM子查询
  • 这种子查询只会执行一次,所以查询效率很高

  • 查询底薪超过公司平均底薪的员工信息

    SELECT
    e.empno,e.ename,e.sal,t.avg
    FROM t_emp e JOIN
    (SELECT deptno,AVG(sal) AS avg
    FORM t_emp 
    GROUP BY deptno) t #按照部门编号去分组 起别名
    ON e.deptno=t.deptno 
    AND e.sal>=t.avg;
    
SELECT子查询
  • 这种子查询每输出一条记录的时候都要执行一次,查询效率很低

    SELECT
    e.empno,
    e.ename,
    (SELECT dname FROM t_dept WHERE deptno=e.deptno)
    FROM t_emp e;
    

数据操作语言:子查询(二)

单行子查询和多行子查询 [结果集可以作为新表连接]
  • 单行子查询的结果集只有一条记录,多行子查询结果集有多行记录

  • 多行子查询只能出现在WHERE子句和FROM子句中

  • 如何用子查询查找FORD和MARTIN两个人的同事

    SELECT ename #排除那俩人之外
    FROM t_emp 
    WHERE
    deptno IN    # deptno = 不行因为后面返回了两条记录
    (SELECT deptno FROM t_emp WHERE ename IN("FORD","MARTIN")); #返回两条记录
    AND ename NOT IN("FORD","MARTIN");
    

WHERE子句中的多行子查询

  • WHERE子句中,可以使用IN、ALL、ANY、EXISTS关键字来处理多行表达式结果集的条件判断

  • 调查”FORD”和”MARTIN”底薪都高的员工信息

    SELECT ename 
    FROM t_emp 
    WHERE sal > ALL #ALL是sal里的数比结果集里的所有值都大  ANY则是比任何一个人大
    (SELECT sal FROM t_emp #结果集里返回了多条记录
    WHERE ename IN("FORD","MARTIN"))
    AND ename NOT IN("FORD","MARTIN"); #不包含这俩人
    

EXISTS关键字

  • EXISTS关键字是把原来在子查询之外的条件判断,写到了子查询的里面
    EXISTS用上之后WHERE就不写任何语句了

    SELECT ... FROM 表名 WHERE [NOT] EXISTS(子查询);
    
  • 查询工资等级是3级或者4级的员工信息

    #排斥此方法 效率低下
    SELECT
    FROM t_emp
    WHERE EXISTS(
    SELECT * FROM t_salgrade
    WHERE sal BETWEEN losal AND hisal 
    AND grade IN(3,4)
    );
    

MySQL对数据的基本操作

数据操作语言:INSERT语句

  • INSERT语句可以向数据表写入记录,可以是一条记录,也可以是多条记录

    INSERT INTO 表名(字段1,字段2,......) #添加字段可以快速写入 
    VALUES(值1,值2,......);
    
    INSERT INTO 表名(字段1,字段2,......) 
    VALUES(值1,值2,......),(值1,值2,......); #多条记录
    
    INSERT INTO t_dept(deptno,dname,loc)
    VALUES(520,"研发部","河北"),(250,"销售部","江西");
    
    #向技术部添加一条员工记录 
    #[技术部编号不知道 子查询技术部的编号 结果 写到VALUES子句里面通过INSERT插入员工表里面]
    INSERT INTO t_emp
    (empno,ename,job,mgr,hiredate,sal,comm,deptno)
    VALUES(8001,"潘春尧","SALESMAN",8000,"2023-10-15",2000,NULL,
    (SELECT deptno FROM t_dept WHERE dname="技术部")); #子查询单个结果返回
    

INSERT语法方言

  • MySQL的INSERT语句有一种方言语法

    INSERT INTO 表名 SET 字段1=值1,字段2=值2,......; #只适合Mysql数据库
    
    (INSERT) INTO t_emp
    SET empno=8002,ename="JACK",job="SLAESMAN",mgr=8000,
    hiredate="1985-01-01",sal=2500,comm=NULL,deptno=50;
    
    DELETE FROM t_emp WHERE empno=8002;
    

IGNORE关键字

  • IGNORE关键字会让INSERT只插入数据库不存在的记录

    INSERT [IGNORE] INTO 表名...;
    INSERT IGNORE INTO t_dept(deptno,dname,loc)
    VALUES(40,"技术部","北京"); #40编号是主键被占用了 和已经现存的记录有冲突
    
    #运用方言写
    INSERT IGNORE INTO t_dept
    SET deptno=40,dname="技术部",loc="北京";
    
    #多重数据忽略不正确的数据
    INSERT IGNORE INTO t_dept(deptno,dname,loc)
    VALUES(40,"A","北京"),(80,"B","上海");
    
    #VALUES(40,"A","北京"),(80,"B","上海")
    #> Affected rows: 1
    #> 时间: 0.002s
    

数据操作语言:UPDATE语句(一)

  • UPDATE语句用于修改表的记录
    UPDATE中的NIGNORE直接忽略冲突的语句

    UPDATE [IGNORE] 表名
    SET 字段1=值1,字段2=值2,......
    [WHERE 条件1...] #有条件的修改记录
    [ORDER BY...] #对数据先排序 后修改员工编号+1 
    [LIMIT...]; #取分页数据
    
    #把每个员工的编号和上司的编号+1,用ORDER BY子句完成
    UPDATE t_emp 
    SET empno=empno+1,mgr=mgr+1
    ORDER BY empno DESC;
    
    #把用收入前三名的员工底薪减100元,用LIMIT子句完成
    UPDATE t_emp
    SET sal=sal-100 
    ORDER BY sal+IFNULL(comm,0) DESC #降序 
    LIMIT 3; #取前三条记录
    

UPDATE语句的表连接(一)

  • 因为相关子查询效率非常低,所以我们可以利用表连接的方式来改造UPDATE语句

    UPDATE 表1 JOIN 表2 ON 条件
    SET 字段1=值1,字段2=值2,......;
    
  • 表连接的UPDATE语句可以修改多张表的记录[进化]

    UPDATE 表1,表2
    SET 字段1=值1,字段2=值2,......
    WHERE 连接条件;
    
    #把ALLEN调往RESEARCH部门,职务调整为ANALYST
    [员工表+部门表]
    UPDATE t_emp e JOIN t_dept d
    SET e.deptno=d.deptno
    WHERE e.ename="ALLEN" AND d.dname="RESEARCH"
    #//在t_emp表中找到ALLEN + 在t_dept表中找到RESEARCH 
    #//然后把d.deptno赋值給e.deptno
    
    UPDATE t_emp e JOIN t_dept d
    SET e.deptno=d.deptno,e.job="ANALYST",d.loc="北京"
    WHERE e.ename="ALLEN" AND d.dname="RESEARCH"
    
  • 把底薪低于公司平均底薪的员工,底薪增加150元
    [运用表连接的方法比WHERE语句的效率高]

    UPDATE t_emp e JOIN
    (SELECT AVG(sal) AS avg FROM t_emp) t
    ON e.sal<t.avg
    SET e.sal=e.sal+150;
    

UPDATE语句的表连接(二)

  • UPDATE语句的表连接既可以是内连接,又可以是外连接

    UPDATE 表1[LEFT|RIGHT] JOIN 表2 ON 条件 
    SET 字段1=值1,字段2=值2,...;
    
  • 把没有部门的员工,或者SALES部门低于2000元底薪的员工,都调往20部门
    [把所有员工保留下来 但是陈浩没有部门 用内连接的话会把陈浩忽略掉 要用左外连接把左表所有数据保留下来再去跟部门表作连接]

    #把没有部门的员工,或者SALES部门低于2000元底薪的员工,都调往20部门
    UPDATE t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno #连接条件 不符合的也保留
    SET e.deptno=202
    WHERE e.deptno IS NULL OR (d.dname="SALES" AND e.sal<2000);
    

数据操作语言: DELETE语句(一)

  • DELETE语句用于删除记录
    DELETE [IGNORE] FROM 表名
    [WHERE 条件1,条件2,...] #按照条件删除记录
    [ORDER BY...] #排序后删除
    [LIMIT...]; #分页 工资降序排序 排在前五名的删掉
    #顺序依次向下 最后一个是DELETE
    
练习1
  • 删除10部门中,工龄超过20年的员工记录

    DELETE FROM t_emp
    WHERE deptno=10 AND DATEDIFF(NOW(),hiredate)/365>=20;
    
  • 删除20部门中工资最高的员工记录

    DELETE FROM t_emp
    WHERE deptno=20
    ORDER BY sal+IFNULL(comm,0) DESC
    LIMIT 1;
    

DELETE语句的表连接(一)

  • 因为相关子查询效率非常低,所有我们可以利用表连接的方法来改造DELETE语句

    DELETE 表1,...FROM 表1 JOIN 表2 ON 条件 #删除哪张表记录的操作
    [WHERE 条件1,条件2,...]
    [ORDER BY...]
    [LIMIT...];
    
    #删除SALES部门和该部门的全部员工记录
    #无需外连接 因为没有部门需要不删除的
    DELETE e,d
    FROM t_emp e JOIN t_dept d NO e.deptno=d.deptno #部门名称
    WHERE d.dname="SALES";
    
  • 删除SALES部门和该部门的全部员工记录[表连接]

    DELETE e
    FROM t_emp e JOIN
    (SELECT deptno,AVG(sal) AS sal FROM t_emp GOURP BY deptno)
    ON e.deptno=t.deptno AND e.sal<t.sal
    
  • 删除员工KING和他的直接下属的员工记录,用表连接实现

    DELETE e
    FROM t_emp e JOIN
    (SELECT empno FROM t_emp WHERE ename="KING") t
    ON e.mgr=t.empno OR e.empno=t.empno; #KING的下属 OR KING这个人
    

DELETE语句的表连接(二)

  • DELETE语句的表连接既可以是内连接,又可以是外连接

    DELETE 表1,... FROM 表1 [LEFT|RIGHT] JOIN 表2 ON 条件...;
    
  • 删除SALES部门的员工,以及没有部门的员工 [左外 因为陈浩没部门要保留 外连接(不可用内连接)]

    DELETE e
    FROM t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno
    WHERE d.dname="SALES" OR e.deptno IS NULL
    

快速删除数据表全部记录

  • DELETE语句实在事务机制下删除记录,删除记录之前,先把将要删除的记录保存到日志文件里面,然后删除记录

  • TRUNCATE语句再事务机制之外删除记录,速度大于DELETE语句

    TRUNCATE TABLE 表名;
    

MySql的内置函数

数据库函数分类:数字、字符、日期、条件函数

函数 功能 用力
ABS 绝对值 ABS(-100)
ROUND 四舍五入 ROUND(4.62)
FOLLR 强制舍位到最近的整数 FLOOR(9,9) = 9
CEIL 强制仅为最近的整数 CEIL(3.2) = 4
POWER 幂函数 POWER(2,3)
LOG 对数函数 LOG(7,3)
LN 对数函数 LN(1,0)
函数 功能 用例
SQRT 开平方 SQRT(9)
PI 圆周率 P10
SIN 三角函数 sin(1)
TAN 三角函数 TAN(1)
COT 三角函数 COT(1)
COS 三角函数 COS(2)
DADIANS 角度来换成角度 DADIANS(20)
DEGREES 弧度转换角度 DEGRESS(1)

获取系统的时间函数

  • NOW()函数能获得系统日期和时间,yyyy-MMMM-dd hh:mm:ss
  • CURDATE()函数能获得当前系统日期,yyyy=MM=ddd
  • CURTIME()函数能获得当前时间系统信息,hh:mm:ss

日期格式化函数(一)

  • **DATE_FORMAT()**函数用于格式化日期,返回用户想要的日期格式

    DATE_FORMAT(r日期,表达式)
    
    SELECT ename,DATE_FORMAT(hiredate,"%Y") AS "year"
    FROM t_emp; 
    

日期格式化函数(二)

占位符 作用 占位符 作用
%Y 年份 %m 月份
%d 日期 %w 星期(数字)
%W 星期(名称) %j 本年第几天
%U 本年第几周 %H 小时(24)
%h 小时(12) %i 分钟
%s %r 时间(12)
%T 时间(24)
  • 利用日期函数,查询明年你的生日是星期几?

    SELECT DATE_FORMAT("2019-6-20","%w"); #数字
    SELECT DATE_FORMAT("2019-6-20","%W"); #英文星期
    
  • 利用日期函数,查询1981年上半年入职的员工多少人?[聚合函数 全表范围不用分组GROUP BY]

    SELECT COUNT(*) FROM t_emp
    WHERE DATE_FORMAT(hiredate,"%Y")=1981
    AND DATE_FORMAT(hiredate,"%m")<=6 #上半年
    

日期计算的注意事项

  • MySQL数据库里面,两个日期不能直接加减,日期也不能与数字加减
日期偏移计算
  • DATE_ADD()函数可以实现日期的偏移计算,而且时间单位很灵活

    DATE_ADD(日期,INTERVAL 偏移量 时间单位)
    
    SELECT DATE_ADD(NOW(),INTERVAL 15 DAY); #15天之后
    SELECT DATE_ADD(NOW(),INTERVAL -300 MINUTE); #300分钟之前  2023-10-16 04:48:48
    SELECT DATE_FORMAT(
    DATE_ADD(DATE_ADD(NOW(),INTERVAL -6 MONTH),INTERVAL -3 DAY),"%Y/%m/%d"); #2023/04/13
    
计算日期之间相隔的天数
  • **DATEDIFF()**函数用来计算两个日期之间相差的天数

    DATEDIFF(日期,日期)
    

字符函数(一)

函数 功能 用例
LOWER 转换小写字符 LOWER(ename)
UPPER 转换大写字符 UPPER(ename)
LENGTH 字符数量 LENGTH(ename)
CONCAT 连接字符串 CONCAT(sal, “$”)
INSTR 字符出现的位置 INSTR(ename, “A”)
INSERT 插入/替换字符 INSERT(“你好”,1,0,”先生”)
替换1个字符 0表示不替换—候补
REPLACE 替换字符 REPLACE(“你好先生”,“先生”,”女士”)
先生换成女士
SELECT
  LOWER(ename),UPPER(ename),LENGTH(ename),CONCAT(sal,"$"),INSTR(ename,"A")
FROM t_emp;

字符函数(二)

函数 功能 用例
SUBSTR 截取字符串 SUBSTR(“你好世界”,3,4)
3开始位置 4结束位置
SUBSTRING 截取字符串 SUBSTRING(“你好世界”,3,2)
3个字符开始往后截取2个位置
LPAD 左侧填充字符 LPAD(“Hello”,10,”“)
* 10最终字符串一共为10个字符
电话隐私保护加※号
RPAD 右侧填充字符 RPAD(“Hello”,10,”*”)
TRIM 去除首位空格 TRIM(“ 你好先生 “)
SELECT LPAD(SUBSTRING("15027597319",8,4),11,"*");
SELECT RPAD(SUBSTRING("潘春尧",1,1),LENGTH("潘春尧")/3,"*"); 
#LENGTH是算英文字符 再除以3就是汉字的字符个数

条件函数

  • SQL语句中可以利用条件函数来实现变成语言里的条件判断

    IFNULL(表达式,值)
    IF(表达式,值1,值2)
    
  • 中秋节公司发放礼品,SALES部门发放礼品A,其余部门发放礼品B,打印每名员工获得的礼品
    [按部门名称作表连接 部门表和员工表 内连接没有部门的没有礼品]

    SELECT 
      e.empno,e.ename,d.dname,
      IF(d.dname="SALES","礼品A","礼品B")
    FROM t_emp e JOIN t_tept d ON e,deptno=d.deptno;
    

条件语句

  • 复杂的条件判断可以用条件语句来实现,比IF语句功能更强大

    CASE 
      WHEN 表达式 THEN 值1
      WHEN 表达式 THEN 值2
      ......
      ELSE 值N
    END
    
  • 公司年庆决定组织员工集体旅游,每个部门旅游目的地是不同的。SALES部门去P1地点,ACCOUNTING部门去P2地点,RESEARCH部门去P3地点,查询每名员工的旅行地点。

    SELECT 
      e.empno,e.ename,
      CASE 
        WHEN d.dname="SALES" THEN "p1"
        WHEN d.dname="ACCOUNTING" THEN "p2"
        WHEN d.dname="RESEARCH" THEN "P3"
    FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno;
    
课堂练习
  • 某公司决定为员工调整基本工资,具体调整方案如下:

    序号 条件 涨幅
    1 SALES部门中工龄超过20年 10%
    2 SALES部门中工龄不满20年 5%
    3 ACCOUNTING部门 +300元
    4 RESEARCH部门里低于部门平均底薪 +200元
    5 没有部门的员工 +100元
    [员工表连接部门表]
    UPDATE t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno
    LEFT JOIN (SELECT deptno,AVG(sal) AS avg FROM t_temp GOURP BY deptno) t
    ON e.deptno=t.deptno
    SET sal=(
        CASE 
          WHEN d,dname="SALES" AND DATEDIFF(NOW(),e.hiredate)/365>=20 
          THEN e.sal*1.1
          WHEN d,dname="ALES" AND DATEDIFF(NOW(),e.hiredate)/365<20 
          THEN e.sal*1.50
          WHEN d,dname="ACCOUNTING" THEN e.sal+300
          WHEN d,dname="RESEARCH" THEN e.sal<t.avg THEN e.sal+200
          WHEN e.deotno IS NULL THEN e.sal+100
          ELSE e.sal
        END
    );
    

事务机制(一) 进入企业市场的第一步

避免写入直接操作数据文件
  • 如果数据的写入直接操作数据文件是非常危险的事情
利用日志来实现间接写入
  • MySQL总共有5种日志,其中只有redo日志和undo日志与事务有关
    [数据库 拷贝数据给 undo日志 记录修改 redo日志 与数据库同步数据]
事务机制(Transaction)
  • RDBMS = SQL语句 + 事务(ACID)
  • 事务是一个或者多个SQL语句组成的整体,要么全部执行成功,要么全都执行失败
事务案例
  • 把10部门中MANGER员工调往20部门,其他岗位的员工调往30部门,然后删除10部门
    事务开启事务 [把要修改的数据拷贝到undo日志[可恢复]内,做的修改会被记录到redo日志[同步]里面] UPDATE语句  DELETE语句  提交事务

管理事务

  • 默认情况下,MySQL执行每条SQL语句都会自动开启和提交事务

  • 为了让多条SQL语句纳入到一个事务之下,可以手动管理事务

    START TRANSACTION;
    SQL语句
    [COMMIT|ROLLBACK];
    
    START TRANSACTION;
    DELETE FROM t_emp
    DELETE FROM t_dept;
    SELECT * FROM t_emp;
    SELECT * FROM t_dept;
    #这些删除修改只是在redo日志文件中进行的修改并未提交 
    #虽然SELECT查不到了 但是点开左列的表中数据仍未删除
    #只要不提交事务 redo日志就不会和数据库做同步
    COMMIT; #把结果提交到日志里面 就会同步了
    ROLLBACK; #做标记 回滚 一起失败
    

事务的ACID属性

原子性 一致性 隔离性 持久性

事务的原子性

  • 一个事务中的所有操作要么全部完成,要么全部失败。事务执行后,不允许停留在中间某个状态

事务的一致性

  • 不管任何给定的时间、并发事务由多少,事务必须保证运行结果的一致性
    [阻止事务之间互相读取临时数据] [A給B 10元 事务没有提交后且回滚了 C给A20元 此时A应该有30元 ]

隔离性

  • 隔离性要求事务不受其他并发事务的影响,如同在给定的时间内,该事务是数据库唯一运行的事务
  • 默认情况下A事务,只能看到日志中该事务的相关数据 [A,B事务可以看undo和redo日志]

持久性

  • 事务一旦提交,结果便是永久性的。即便发生宕机,仍然可以依靠事务日志完成数据的持久化

事务机制(二)

事务的四个隔离级别(可设置相互读取)

序号 隔离级别 功能
1 read uncommitted 读取未提交数据
2 read committed 读取已提交数据
3 repeatable read 重复读取
4 serializable 序列化

业务案例1 [购票系统]

A事务 B事务
车次 车厢 坐席 状态
G8047 1 1A 未售出
G8047 1 1B 未售出

A事务看到G8047 1A坐席未售出 用UPDATE把状态修改成已售出 因为没有提交 所以只修改在了undo日志里 真实的数据没有发生改变。此时B事务启动了看到了1A坐席还有票 于是更新且提交了 于是数据已经发生了改变。

A事务
车次 车厢 坐席 状态
G8047 1 1A 已售出
G8047 1 1B 未售出
所以在这个案例中我们需要B事务去读取A事务的状态 发现在A事务的临时数据里购买了坐席 那么B事务就可以去购买其他的坐席

修改事务隔离级别

  • READ UNCOMMITTED 代表可以读取其他事务未提交的数据

    # ↓设置当前绘画的事务级别↓ [并非全局]
    SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    
    *无标题-查询1
    START TRANSACTION;
    UPDATE t_emp SET sal=1;
    -----------------------
    *无标题-查询2
    START TRANSACTION;
    SELECT empno,enam,sal FROM t_emp;
    #发现员工的sal并未变成1块钱
    
    #解决问题:增加事务隔离级别  一个事务读到了另一个事务的数据
    SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    START TRANSACTION;
    SELECT empno,enam,sal FROM t_emp;
    COMMIT;
    

业务案例2 [银行转账]

Scott账户有5000元余额 B事务开始执行支出100元 此时A事务执行转账1000元到该用户 如果AB都正常commit 最终账户余额是5900元 但是如果B事务是一个错误的消费被回滚[退款操作] 最终账户应该是6000元。如果允许A事务去读取B事务的临时数据 按照4900+1000=5900元 若A回滚就凭空少了100元

修改事务隔离级别

  • READ COMMITTED 代表只能读取其他事务提交的数据

    # ↓设置当前绘画的事务级别↓ [并非全局]
    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
    

业务案例3 [电商案例]

A事务 下单购买 商品(价格350元) B事务要对 该商品涨价

修改事务隔离级别【READ为默认级别】

  • REPEATABLE READ 代表事务在执行中反复读取数据,得到的结果是一致性的,不会受其他事务影响

    #就算事务A提交了数据 也不影响事务B的查询原始数据
    SET SESSION TRANSACTION ISOLATION LEVEL REPEATALE READ;
    
START TRANSACTION; 
UPDATE t_emp SET sal=1; 
COMMIT;
-----------------------
SET SESSION TRANSACTION ISOLATION LEVEL REPEATALE READ;
START TRANSACTION; 
SELECT empno,ename,sal FROM t_emp; 
COMMIT;

事务的序列化

  • 由于事务并发执行所带来的各种问题,前三种隔离级别只适用于在某些业务场景中,但是序列化的隔离性,让事务逐一执行,就不会产生上述问题了。

    SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    
START TRANSACTION; ①
UPDATE t_emp SET sal=1; ②
COMMIT; ⑤
-----------------------
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION; ③
SELECT empno,ename,sal FROM t_emp; ④ #此时会等待 只需要执行上方COMMIT
COMMIT;

数据导入与导出

数据导出与备份的区别

  • 数据导出,导出的纯粹是业务数据
  • 数据备份,备份的是数据文件、日志文件、索引文件灯

全量备份 → 增量备份1 → 增量备份2

数据导出的分类

数据库可以导出SQL文档(数据不多) 也可以 导出文本文档(数据巨大)

导出SQL文件 [或者手动右键库 存储SQL文件]

  • mysqldump用来把业务数据导出成SQL文件,其中也包括了表结构

    #导出命令行 不写no-date就导出即包含 表结构 又包含 数据
    mysqldump -uroot -p [no-date] 逻辑库 > 路径
    # D:/MySQL/demo.sql
    

导入SQL文件

  • source命令用于导入SQL文件,包括创建数据表,写入记录等
    sql > USE demo;
    sql > SOURCE backup.sql;
    
导出纯粹的业务数据

若数据巨大 先导出表结构 再导出数据库文档(.txt) 之后再导入 就会快很多

  • t_emp 右键 存储sql文件 仅结构 再次右键 导出向导… txt文档 (跳过词法分析与语法优化)

    "empno"    "ename"    "job"    "mgr"    "hiredate"    "sal"    "comm"    "deptno"
    "7369"    "SMITH"    "CLERK"    "7902"    "17/12/1980"    "800"        "20"
    "7499"    "ALLEN"    "SALESMAN"    "7698"    "20/2/1981"    "1600"    "300"    "30"
    "7521"    "WARD"    "SALESMAN"    "7698"    "22/2/1981"    "1250"    "500"    "30"
    "7566"    "JONES"    "MANAGER"    "7839"    "2/4/1981"    "2975"        "20"
    "7654"    "MARTIN"    "SALESMAN"    "7698"    "28/9/1981"    "1250"    "1400"    "30"
    "7698"    "BLAKE"    "MANAGER"    "7839"    "1/5/1981"    "2850"        "30"
    "7782"    "CLARK"    "MANAGER"    "7839"    "9/6/1981"    "2450"        "10"
    "7788"    "SCOTT"    "ANALYST"    "7566"    "9/12/1982"    "3000"        "20"
    "7839"    "KING"    "PRESIDENT"        "17/11/1981"    "5000"        "10"
    "7844"    "TURNER"    "SALESMAN"    "7698"    "8/9/1981"    "1500"    "0"    "30"
    "7876"    "ADAMS"    "CLERK"    "7788"    "12/1/1983"    "1100"        "20"
    "7900"    "JAMES"    "CLERK"    "7698"    "3/12/1981"    "950"        "30"
    "7902"    "FORD"    "ANALYST"    "7566"    "3/12/1981"    "3000"        "20"
    "7934"    "MILLER"    "CLERK"    "7782"    "23/1/1982"    "1300"        "10"
    
  • 导入回来 先删除t_emp 在dmeo右键 运行文件 导入刚刚右键存储的t_emp的结构(仅结构) 此时员工表结构就回来了 导入向导… 下一步 找到导出的txt文档 第一个数据行1(第一行有效数据) 在导入向导中根据源字段匹配目标字段 (txt+结构=数据表)

阅读全文

油画项目(介于JavaWeb与数据库之间)

2023/10/1

企业门户网站项目实战

  • 需求说明与环境准备
  • 实现前端展示模块
  • 实现后台数据管理模块

(MVC架构模型) 数据显示与数据处理分开

Model模型 View试图 Controller控制器 [是一种设计理念]

Model - 模型
  • 模型(Model)负责生产业务需要的数据 Service[结尾]实现业务逻辑

    public class MathService{
        public List square(int max){
            List list = new ArrayList();
            for(int i = 0; i <= max; i++){
                long result = i * i;
                list.add(result);
            }
            return list;
        }
    }
    
Controller - 控制器 (Java Web领域是WebServlet)
  • 控制器(Controller)是界面(View)与模型(Model)的粘合剂
@WebServlet("/square")
public class HelloWord  extends HttpServlet{
    public void doGet(HttpServletRequest req, HttpServletResponse res){
        //1.接收数据 接收web的http请求参数
        int max = Integer.parselnt(request.getParameter("max"));
        //2.调用处理类(模型)进行处理 
        MathService ms = new MathService();
        List list = ms.square(max);//前台调用的参数传入square方法中
        req.setAttribute("squareList", list);//执行结果放到请求属性中
        //3.跳转界面(View)
        request.getRequestDispatcher("/result.jsp").forward(req,res);
    }
}
View - 视图
  • 试图(View)用于展示最终结果
URL:http://localhost:8080/square?max=100
----------------------------------------
<ul>
<c:foreach items = "${squareList}" var = "r" varStatus = "idx">
   <li>${idx.index}的平方是${r}</li>
</<c:foreach>
</ul>

MVC架构模式优点

  • 保证了软件层面的解耦,同时保障了团队合作的组织架构

  • 软件团队分工合作,成员各司其职 通过controller联合

  • 组件可灵活替代,互不影响

通过控制器将前端传入的参数进行接收调用后台的模型产生结果,结果通过控制器保存在当前的属性中在视图中进行展现

工程结构与开发规约

工程结构
mgallery - eclipse工程项目
 /src - java源代码目录
 /WebContent - Web资源目录
 /css - css文件目录
 /js - js文件目录
 /image - 图片资源目录
 /upload - 上传文件目录
 /WEB-INF   //jsp数据来自controller 不允许在web中直接访问 要从控制器跳转
   /jsp - jsp页面目录
   /lib - jar文件目录
   /classes - 编译后的class目录
   /web.xml web描述符文件
包结构
com.imooc.mgallery //逆命名法
    /controller - 存放Servlet控制器类 //承上启下接收参数 调用逻辑 返回处理结果
    /service - 存放处理逻辑类model[伪数据库] //完成业务逻辑 service与dao进行传递调用
    /dao - Data Access Object 数据访问对象类 数据读写的java类 数据来自xml文件
    /entity - 存放实体类 JavaBean java中的简单对象
    /utils - 通用工具类 底层通用的工具类或方法

Dao类[Data Access Object]

  • XxxDao类只负责对数据进行读取、写入操作
  • 只负责对数据 增、删、改、查
示例:PaintingDao //针对油画数据进行增删改查
public class PaintingDao{
    public void append(){...} //新增数据
    public void update(){...} //修改数据
    public void delete(){...} //删除数据
    public void findAll(){...} //查询数据
}

Service与Dao的关系

  • Service负责进行流程处理,需**持久化[java处理在内存中 存储在数据库防止丢失]**时调用Dao
  • Dao只负责单纯对数据进行增删改查操作

JavaBean

  • 对一种类的使用形式的统称

  • JavaBean是一种Java中可重用组件

  • JavaBean不是技术,而是一种Java类的格式要求

  • JavaBean在Java领域非常常见,通常用于存储数据

JavaBean格式要求

  • 类必须是pubilc并提供默认构造函数
  • 所有属性private私有化
  • 属性通过getter与setter方法进行读写
public class Painting{//类公有化
    public Painting(){...}; //提供默认构造函数,可不写
    private Integer id; //属性私有
    private String pname;
    public Integer getId(){return id;} //getter获取属性值
    public void setId(Integer id){this.id = id;} //setter设置属性值
    public String getPname(){return pname;}
    public void setPname(String pname){this.pname = pname;}
}

创建mgallery工程(ViewControllerServiceDao){VCSD}

  • 开发PaintingDao读取XML数据,实现分页操作
  • 开发PaintingService服务类,对PaintingDao进行调用
  • 开发PaintingController控制器,调用PaintingService
  • 重写index.html,利用JSP技术读取分页数据

关键类与方法

  • XmlDataSource类 - XML数据源工具类,简化Dao提取操作
  • PaintingDao.pagination() - 数据分页查询方法
  • PageModel类 - 分页结果的包装类

Dom4j

  • Dom4j是一个易用的、开源的库,用于解析XML。它应用于Java平台
  • Dom4j将XML视为Document对象
  • XML标签被Dom4j定义为Element对象
Dom4j开发流程回顾
  • SAXReader.read()读取XML文件,得到Document对象
  • document.selectNodes()利用Xpath得到XML节点集合
  • 遍历XML节点,包装成JavaBean或者集合对象返回

开发XmlDataSource (utils)

【详细】IntelliJ IDEA: 无法创建Java Class文件_idea 不能新建java class-CSDN博客
IDEA部署Tomcat提示:Warning no artifacts configured-CSDN博客
Class.getResource(“xxx.css”)得到值为null-CSDN博客

utils/XmlDataSource.java
package com.example.mgallery.utils;

import com.example.mgallery.entity.Painting;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

//用于将XML文件解析为Java对象
public class XmlDataSource {
    //通过static静态关键字保证数据全局唯一
    private static List data = new ArrayList(); //油画集合
    private static String dataFile;
    static{ //程序运行以后去得到类路径目录下的/painting.xml的路径地址
        dataFile = XmlDataSource.class.getResource("/painting.xml").getPath();
        System.out.println(dataFile);
          //若得到特殊字符进行编码转换 空格 c:\new style\painting.xml
        try {
            URLDecoder.decode(dataFile, "UTF-8");
            //利用Dom4j对XML进行解析 读取XML
            SAXReader reader = new SAXReader();
            //1.获取Document文档对象
            Document document = reader.read(dataFile);
            //2.Xpath得到XML节点集合 获取多个xml节点
            List<Node> nodes = document.selectNodes("/root/painting");
            for (Node node : nodes){
                Element element = (Element) node;
                String id = element.attributeValue("id");//获得id
                String pname = element.elementText("pname");//获得子节点
                Painting painting = new Painting();
                painting.setId(Integer.parseInt(id));
                painting.setPname(pname);
                painting.setCategory(Integer.parseInt(element.elementText("category")));
                painting.setPrice(Integer.parseInt(element.elementText("price")));
                painting.setPreview(element.elementText("preview"));
                painting.setDescription(element.elementText("description"));
                data.add(painting);//将List data 保存油画的完整信息

                System.out.println(id + ";" + pname);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
    /**
     *  获取所有油画Painting对象
     * @return Painting List
     */
    public static List<Painting> getRawData(){
        return data;
    }
    public static void main(String[] args) {
//        new XmlDataSource();
        List<Painting> ps = XmlDataSource.getRawData();
        System.out.println(ps);
    }
}
META-INF/painting.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 数据文件 -->
<root>
    <painting id="1">
        <pname>古典油画鉴赏1</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/1.jpg</preview>
        <description>古典油画鉴赏1的描述文本</description>
    </painting>
    <painting id="2">
        <pname>古典油画鉴赏2</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/2.jpg</preview>
        <description>古典油画鉴赏2的描述文本</description>
    </painting>
    <painting id="3">
        <pname>古典油画鉴赏3</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/3.jpg</preview>
        <description>古典油画鉴赏3的描述文本</description>
    </painting>
    <painting id="4">
        <pname>古典油画鉴赏4</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/4.jpg</preview>
        <description>古典油画鉴赏4的描述文本</description>
    </painting>
    <painting id="5">
        <pname>古典油画鉴赏5</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/5.jpg</preview>
        <description>古典油画鉴赏5的描述文本</description>
    </painting>
    <painting id="6">
        <pname>古典油画鉴赏6</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/6.jpg</preview>
        <description>古典油画鉴赏6的描述文本</description>
    </painting>
    <painting id="7">
        <pname>古典油画鉴赏7</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/7.jpg</preview>
        <description>古典油画鉴赏7的描述文本</description>
    </painting>
    <painting id="8">
        <pname>古典油画鉴赏8</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/8.jpg</preview>
        <description>古典油画鉴赏8的描述文本</description>
    </painting>
    <painting id="9">
        <pname>古典油画鉴赏9</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/9.jpg</preview>
        <description>古典油画鉴赏9的描述文本</description>
    </painting>
    <painting id="9">
        <pname>古典油画鉴赏9</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/9.jpg</preview>
        <description>古典油画鉴赏9的描述文本</description>
    </painting>
    <painting id="10">
        <pname>抽象派油画鉴赏1</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/10.jpg</preview>
        <description>抽象派油画鉴赏1的描述文本</description>
    </painting>
    <painting id="11">
        <pname>抽象派油画鉴赏2</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/11.jpg</preview>
        <description>抽象派油画鉴赏2的描述文本</description>
    </painting>
    <painting id="12">
        <pname>抽象派油画鉴赏3</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/12.jpg</preview>
        <description>抽象派油画鉴赏3的描述文本</description>
    </painting>
    <painting id="13">
        <pname>抽象派油画鉴赏4</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/13.jpg</preview>
        <description>抽象派油画鉴赏4的描述文本</description>
    </painting>
    <painting id="14">
        <pname>抽象派油画鉴赏5</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/14.jpg</preview>
        <description>抽象派油画鉴赏5的描述文本</description>
    </painting>
    <painting id="15">
        <pname>抽象派油画鉴赏6</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/15.jpg</preview>
        <description>抽象派油画鉴赏6的描述文本</description>
    </painting>
    
</root>
entity/Painting.java
public class Painting{
    private Integer id;
    private String pname;
    private Integer category;
    private Integer price;
    private String preview; //油画图片地址
    private String description; //描述
    ......
}

开发PageMode1 (utils)

utils/PageModel.java
public class PageModel {
    private int page;
    private int totalPages;
    private int rows; //每页几条数据
    private int totalRows; //原始数据多少条
    private int pageStartRow; //当前页是从第几行开始的
    private int pageEndRow; //到第几行结束的 结束行号
    private boolean hasNextPage; //是否下一页(尾页)
    private boolean hasPreviousPage; //是否上一页(首页)
    private List pageData; //当前页面数据

    public PageModel() {
    }

    /**
     * 初始化PageMode1对象,计算分页属性
     * @param page
     * @param rows
     * @param data
     */
    public PageModel(List data, int page, int rows) {
        this.page = page;
        this.rows = rows;
        totalRows = data.size();
        //总页数计算规则:总行数/每页记录数,能整除页数取整,不能整除向上取整
        //18/6=3 | 2/6≈3.33 向上取整=4  intValue()得到整数部分
        //Nath.ceil向上取整  Math.floor浮点数向下取整
        //小细节: 20/6≈3.33 但是totalRows 和 rows都是整数 20/6=3 向上取整还是3
        //仅需在一个后面×1f即可解决问题 (rows*1f)默认类型转换返回浮点数
        totalPages = new Double(Math.ceil(totalRows/(rows * 1f))).intValue();
        pageStartRow = (page - 1) * rows; //0
        pageEndRow = page * rows; //6
        //totalRows:20 | totalPage:4 | rows:6
        //pageEndRow=4*6=24>20 执行subList()抛出下标越界异常
        if (pageEndRow > totalRows){
            pageEndRow = totalRows; //让20作为结束行号 不会越界
        }
        pageData = data.subList(pageStartRow, pageEndRow); //得到分页数据
        if (page > 1) {
            hasPreviousPage = true;
        }else {
            hasPreviousPage = false;
        }
        if (page < totalPages) { //判断是否存在下一页
            hasNextPage = true;
        }else {
            hasNextPage = false;
        }
    }

    public static void main(String[] args) {
        List sample = new ArrayList();
        for (int i = 1; i < 100; i++) {
            sample.add(i);
        }
        PageModel pageModel = new PageModel(sample,6,8);
        System.out.println(pageModel.getPageData()); //当前页的list集合
        System.out.println(pageModel.getTotalPages());
        System.out.println(pageModel.getPageStartRow() + ":" + pageModel.getPageEndRow()) ;
    }
+getter and setter 

油画数据分页设计思路

@Param注解的使用方法

@Param的作用就是給参数命名,比如在mapper里面某个方法A(int id),当添加注解后A(@Param(“userId”) int id),也就是说外部想要取出传入的id值,只需要取它的参数名userId就可以了。若在SQL中,通过#{userId}进行取值給SQL的参数赋值。

快速添加注解只需要在前面 /** + 回车

油画数据分页展示

开发PaintingDao与PaintingService
service/PaintingDao.java
package com.example.mgallery.dao;

import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;
import com.example.mgallery.utils.XmlDataSource;

import java.util.List;

//获得最原始的 对其进行分页处理
public class PaintingDao {
    public PageModel pagination(int page, int rows){
        //Painting油画对象集合
        List<Painting> list = XmlDataSource.getRawData();
        //PageModel分页处理得到分页数据及分页附加
        PageModel pageModel = new PageModel(list,page,rows);
        return pageModel;
    }
}
service/PaintingService
package com.example.mgallery.service;

import com.example.mgallery.dao.PaintingDao;
import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;

import java.util.List;
//完成业务逻辑 service与dao进行传递调用
public class PaintingService {
    private PaintingDao paintingDao = new PaintingDao();
    public PageModel pagination(int page, int rows){
        if (rows == 0){
            throw new RuntimeException("无效的rows参数");
        }
        return paintingDao.pagination(page, rows); //调用并返回
    }

    public static void main(String[] args) {
        PaintingService paintingService = new PaintingService();
        PageModel pageModel = paintingService.pagination(2, 6);//每页显示六条
        List<Painting> paintingList = pageModel.getPageData();
        for (Painting painting : paintingList){
            System.out.println(painting.getPname());
        }
        System.out.println(pageModel.getPageStartRow() + ":" + pageModel.getPageEndRow());

    }
}
开发PaintingController控制器
controller/PaintingController.java
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/page")
public class PaintingController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();
    public PaintingController(){
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.接收Http数据
        String page = req.getParameter("p"); //页号
        String rows = req.getParameter("r"); //每页记录数
        if (page == null){
            page = "1"; //没传入则默认查询第一个
        }if (rows == null){
            rows = "6"; //每页默认显示六条数据
        }
        //2.调用Service方法,得到处理结果
        PageModel pageModel = paintingService.pagination(Integer.parseInt(page), Integer.parseInt(rows));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/src/main/webapp/index.jsp").forward(req,resp); //跳转jsp
    }
}

前台门户首页(由静态转换为动态)

HTML转换JSP变更流程
  • 打开HTML文件,在首行增加 < %@page contentType % >
  • 更改扩展名从 .html.jsp,移动到WEB-INF/jsp目录下
  • 提示:JSP不要实用<%%>代码块,实用EL+JSTL提取数据

输入(http://localhost:8080/)不能正常访问,但是添加项目名后可以访问-CSDN博客
实例化servlet类[web.LoginServlet]异常 servle-CSDN博客

WEB-INF/jsp/index.jsp
<%@page contentType = "text/html;charset=utf-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="css\common.css">
    <script type="text/javascript" src="js\js1.js"></script>
</head>
<body>
<div class="header">
    <div class="logo">
        <img src="image\logo.png">
    </div>
    <div class="menu" onclick="show_menu()" onmouseleave="show_menu1()">
        <div class="menu_title" ><a href="###">内容分类</a></div>
        <ul id="title">
            <li>现实主义</li>
            <li>抽象主义</li>
        </ul>
    </div>
    <div class="auth">
        <ul>
            <li><a href="#">登录</a></li>
            <li><a href="#">注册</a></li>
        </ul>
    </div>
</div>
<div class="content">
    <div class="banner">
        <img src="image/welcome.png" class="banner-img">
    </div>
    <div class="img-content">
        <ul>
            <c:forEach items="${pageModel.pageData}" var="painting"> <!--pageModel.java-->
                <li>
                    <img src="${painting.preview}" class="img-li">  <!--painting.java-->
                    <div class="info">
                        <h3>${painting.pname}</h3>
                        <p>
                                ${painting.description}
                        </p>
                        <div class="img-btn">
                            <div class="price"><fmt:formatNumber pattern="¥0.00" value="${painting.price}"></fmt:formatNumber></div>
                            <a href="#" class="cart">
                                <div class="btn">
                                    <img src="image/cart.svg">
                                </div>
                            </a>
                        </div>
                    </div>
                </li>
            </c:forEach>
        </ul>
    </div>
    <div class="page-nav">
        <ul>
            <li><a href="/mgallery/page?p=1">首页</a></li> <!--当前页减一就是上一页 否则就是1-->
            <li><a href="/mgallery/page?p=${pageModel.hasPreviousPage?pageModel.page-1:1}">上一页</a></li>
            <c:forEach begin="1" end="${pageModel.totalPages}" var="pno" step="1">
                <li><span ${pno==pageModel.page?"class='first-page'":""}> <!--选中的页才有⚪圈圈 三目运算符不满足产生空字符串-->
                    <a href="/mgallery/page?p=${pno}">
                            ${pno}
                    </a></span></li>
            </c:forEach>
            <li><a href="/mgallery/page?p=${pageModel.hasNextPage?pageModel.page+1:pageModel.totalPages}">下一页</a></li>
            <li><a href="/mgallery/page?p=${pageModel.totalPages}">尾页</a></li>
        </ul>
    </div>
</div>
<div class="footer">
    <p><span>P_luminary</span>©2023.10.3 POWERED BY GITHUB.COM</p>
</div>
</body>
</html>

实现分类查询功能 [改造代码]

从Dao层面进行对数据的过滤

PaintingDao.java
package com.example.mgallery.dao;

import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;
import com.example.mgallery.utils.XmlDataSource;

import java.util.ArrayList;
import java.util.List;

//获得最原始的 对其进行分页处理
public class PaintingDao {
    public PageModel pagination(int page, int rows){
        //Painting油画对象集合
        List<Painting> list = XmlDataSource.getRawData();
        //PageModel分页处理得到分页数据及分页附加
        PageModel pageModel = new PageModel(list,page,rows);
        return pageModel;
    }

    public PageModel pagination(int catagory, int page, int rows){ //int catagory添加Dao层对数据进行筛选
        List<Painting> list = XmlDataSource.getRawData();
        List<Painting> categoryList = new ArrayList();
        for (Painting p : list){
            //如果等于从外侧添加的筛选条件 则添加categoryList内
            if (p.getCategory() == catagory){
                categoryList.add(p);
            }
        }
        PageModel pageModel = new PageModel(categoryList,page,rows);
        return  pageModel;
    }
}
----------------------------------------------------------------------
PaintingService.java
package com.example.mgallery.service;

import com.example.mgallery.dao.PaintingDao;
import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;

import java.util.List;
//完成业务逻辑 service与dao进行传递调用
public class PaintingService {
    private PaintingDao paintingDao = new PaintingDao();
    public PageModel pagination(int page, int rows, String...category){  //最后一个是添加 可选参数(可能/不一定出现一个或多个)
        if (rows == 0){
            throw new RuntimeException("无效的rows参数");
        }
        if (category.length==0 || category[0] == null){
        return paintingDao.pagination(page, rows); //调用并返回
        }else { //程序进行可选调用 两个不同路径 尽量不要去修改类结构
            return paintingDao.pagination(Integer.parseInt(category[0]), page, rows);
        }
    }

    public static void main(String[] args) {
        PaintingService paintingService = new PaintingService();
        PageModel pageModel = paintingService.pagination(2, 6);//每页显示六条
        List<Painting> paintingList = pageModel.getPageData();
        for (Painting painting : paintingList){
            System.out.println(painting.getPname());
        }
        System.out.println(pageModel.getPageStartRow() + ":" + pageModel.getPageEndRow());

    }
}
---------------------------------------------------------------------
PaintingController.java
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class PaintingController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();
    public PaintingController(){
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.接收Http数据
        String page = req.getParameter("p"); //页号
        String rows = req.getParameter("r"); //每页记录数
        String category = req.getParameter("c");
        if (page == null){
            page = "1"; //没传入则默认查询第一个
        }if (rows == null){
            rows = "6"; //每页默认显示六条数据
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(page), Integer.parseInt(rows), category);
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(req,resp); //跳转jsp
    }
}
---------------------------------------------------------------------
webapp/WEB-INF/jsp/index.jsp
<%@page contentType = "text/html;charset=utf-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="css\common.css">
    <script type="text/javascript" src="js\js1.js"></script>
</head>
<body>
<%--JSTL混合书写判断 判断c是否存在 存在追加链接--%>
<c:if test="${param.c != null}">
    <c:set var = "categoryParam" value="&c=${param.c}"></c:set>
</c:if>
<c:if test="${param.c == null}">
    <c:set var = "categoryParam" value=""></c:set>
</c:if>
<div class="header">
    <div class="logo">
        <img src="image\logo.png">
    </div>
    <div class="menu"   onclick="show_menu()" onmouseleave="show_menu1()">
        <div class="menu_title" ><a href="###">内容分类</a></div>
        <ul id="title">
            <li><a href="/mgallery/page?c=1">现实主义</a></li>
            <li><a href="/mgallery/page?c=2">抽象主义</a></li>
        </ul>
    </div>
    <div class="auth">
        <ul>
            <li><a href="#">登录</a></li>
            <li><a href="#">注册</a></li>
        </ul>
    </div>
</div>
<div class="content">
    <div class="banner">
        <img src="image/welcome.png" class="banner-img">
    </div>
    <div class="img-content">
        <ul>
            <c:forEach items="${pageModel.pageData}" var="painting">
                <li>
                    <img src="${painting.preview}" class="img-li">
                    <div class="info">
                        <h3>${painting.pname}</h3>
                        <p>
                                ${painting.description}
                        </p>
                        <div class="img-btn">
                            <div class="price"><fmt:formatNumber pattern="¥0.00" value="${painting.price}"></fmt:formatNumber></div>
                            <a href="#" class="cart">
                                <div class="btn">
                                    <img src="image/cart.svg">
                                </div>
                            </a>
                        </div>
                    </div>
                </li>
            </c:forEach>
        </ul>
    </div>
    <div class="page-nav">
        <ul>
            <li><a href="/mgallery/page?p=1${categoryParam}">首页</a></li> <!--当前页减一就是上一页 否则就是1-->
            <li><a href="/mgallery/page?p=${pageModel.hasPreviousPage?pageModel.page-1:1}${categoryParam}">上一页</a></li>
            <c:forEach begin="1" end="${pageModel.totalPages}" var="pno" step="1">
                <li><span ${pno==pageModel.page?"class='first-page'":""}> <!--选中的页才有⚪圈圈 三目运算符不满足产生空字符串-->
                     <%--  c不存在,则href="/mgallery/page?p=1"  c存在,测href="/mgallery/page?p=1&c=1"  --%>
                    <a href="/mgallery/page?p=${pno}${categoryParam}">
                            ${pno}
                    </a></span></li>
            </c:forEach>
            <li><a href="/mgallery/page?p=${pageModel.hasNextPage?pageModel.page+1:pageModel.totalPages}${categoryParam}">下一页</a></li>
            <li><a href="/mgallery/page?p=${pageModel.totalPages}${categoryParam}">尾页</a></li>
        </ul>
    </div>
</div>
<div class="footer">
    <p><span>P_luminary</span>©2023.10.3 POWERED BY GITHUB.COM</p>
</div>
</body>
</html>
利用默认首页跳转到任何地址上 [常用开发技巧]
index.html
<script>
    window.location.href="/page"
</script>

实现后台数据管理

前台与后台的区别
前台系统 后台系统
开放度 对外开放 不对外开放
面向群体 客户 企业内部人员
功能 提供查询与交互 管理前台数据
设计侧重点 良好的用户体验 严密的业务逻辑
访问量
典型案例 猫眼电影网 [后台有评论审核系统] XX公司无纸质化办公系统
后台实现功能

油画列表 油画预览 删除油画 修改油画 新增与上传油画

实现油画列表功能

重用pagination方法实现分页列表

原有的PaintingController不可以继续被用 术业有专攻 要再创建一个使用

ManagementController.java
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();
    public ManagementController() {
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        String method = req.getParameter("method");
        if (method.equals("list")){ //去显示分页的数据
            this.list(req, resp);
        } else if (method.equals("delete")) {
            // this.delete(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p==null){
            p = "1";
        }
        if (r==null){
            r = "6";
        }
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel", pageModel);
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req, resp);
    }
}
management.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>门户数据管理平台</title>
    <style>
        body{
            margin: 0 auto;
        }
        a{
            text-decoration: none;
        }
        .left{
            float: left;
        }
        .right{
            float: right;
        }
 
        .pg_header{
            position: fixed;
            top: 0;
            width: 100%;
            height: 48px;
            background-color: lightseagreen;
            color: white;
            z-index: 10;
        }
        .pg_header .logo{
            height: 48px;
            width: 200px;
            text-align: center;
            color: white;
        }
        .pg_header .logo a img{
            height: 48px;
            width: 200px;
            cursor: pointer;
        }
        .pg_header .person_info{
            position: relative;
            height: 48px;
            width: 160px;
            /*text-align: center;*/
        }
        .pg_header .person_info img{
            border: 0;
            height: 48px;
            width: 50px;
            /*使用border-radius可以定义边框的圆角程度*/
            border-radius: 50%;
        }
        .pg_header .person_info .info{
            position: absolute;
            width: 150px;
            background-color: lightseagreen;
            top: 50px;
            z-index: 20;
            display: none;
        }
        .pg_header .person_info .info a{
            display: block;
            color: white;
            padding: 5px;
        }
        .pg_header .person_info:hover .info{
            display: block;
        }
        .pg_header .icons{
            line-height: 48px;
            padding: 0 20px 0 5px;
        }
        .pg_header .icons:hover{
            background-color: lightseagreen;
        }
        .pg_header .icons span{
            padding: 1px 5px;
            line-height: 1px;
            border-radius: 50%;
            background-color: red;
            font-size: 12px;
        }
        .pg_content .menu{
            position: absolute;
            top: 50px;
            left: 0;
            bottom: 0;
            width: 300px;
            border:0px;
            border-right: 1px solid #ccc;
        }
        .pg_content .content{
            position: absolute;
            top: 50px;
            right: 0;
            bottom: 0;
            left: 302px;
            overflow: auto;
            min-width: 980px;
            z-index: 8;
            border:0px;
            overflow: hidden;
        }
        .menu_item{
            display: block;
            padding: 10px 20px;
            border-bottom: 1px solid #ccc;
            font-size: 20px; 
            color: #666666;
        }
        
        .menu_item:hover{
            color: white;
            background: lightseagreen;
        }
    </style>
</head>
<body>
    <!-- 顶端导航栏 -->
    <div class="pg_header">
        <!-- Logo与名称 -->
        <div class="logo left">
            <a href="javascript:void(0)" target="_blank">
                <img src="image/logo_1.png">    
            </a>
            
        </div>
        
        <!-- 用户头像与菜单 -->
        <div class="person_info right" style="vertical-align: top;" >
            <img src="image/head.png">
            <span style="line-height: 50px;vertical-align: top;">小企鹅</span>
            <div class="info">
                <a href="javascript:void(0)">我的信息</a>
                <a href="javascript:void(0)">修改密码</a>
                <a href="javascript:void(0)">注销</a>
            </div>
        </div>
        <div class="icons right">
            <i class="far fa-bell"></i>
        </div>
    </div>
    <div class="pg_content">
        <!-- 左侧功能区菜单 -->
        <div class="menu">
            <a href = "#" class="menu_item" target="ifcontent">油画列表</a>
            <a href = "#" class="menu_item" target="ifcontent">新增作品</a>
        </div>
        <!-- 主体框架 -->
        <div class="content">
             <iframe name="ifcontent" style="width:100%;height:100%;overflow-y: hidden;border:0px;min-width: 800px;" src=""></iframe>
        </div>
    </div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>门户数据管理平台</title>
    <style>
        body{
            margin: 0 auto;
        }
        a{
            text-decoration: none;
        }
        .left{
            float: left;
        }
        .right{
            float: right;
        }

        .pg_header{
            position: fixed;
            top: 0;
            width: 100%;
            height: 48px;
            background-color: lightseagreen;
            color: white;
            z-index: 10;
        }
        .pg_header .logo{
            height: 48px;
            width: 200px;
            text-align: center;
            color: white;
        }
        .pg_header .logo a img{
            height: 48px;
            width: 200px;
            cursor: pointer;
        }
        .pg_header .person_info{
            position: relative;
            height: 48px;
            width: 160px;
            /*text-align: center;*/
        }
        .pg_header .person_info img{
            border: 0;
            height: 48px;
            width: 50px;
            /*使用border-radius可以定义边框的圆角程度*/
            border-radius: 50%;
        }
        .pg_header .person_info .info{
            position: absolute;
            width: 150px;
            background-color: lightseagreen;
            top: 50px;
            z-index: 20;
            display: none;
        }
        .pg_header .person_info .info a{
            display: block;
            color: white;
            padding: 5px;
        }
        .pg_header .person_info:hover .info{
            display: block;
        }
        .pg_header .icons{
            line-height: 48px;
            padding: 0 20px 0 5px;
        }
        .pg_header .icons:hover{
            background-color: lightseagreen;
        }
        .pg_header .icons span{
            padding: 1px 5px;
            line-height: 1px;
            border-radius: 50%;
            background-color: red;
            font-size: 12px;
        }
        .pg_content .menu{
            position: absolute;
            top: 50px;
            left: 0;
            bottom: 0;
            width: 300px;
            border:0px;
            border-right: 1px solid #ccc;
        }
        .pg_content .content{
            position: absolute;
            top: 50px;
            right: 0;
            bottom: 0;
            left: 302px;
            overflow: auto;
            min-width: 980px;
            z-index: 8;
            border:0px;
            overflow: hidden;
        }
        .menu_item{
            display: block;
            padding: 10px 20px;
            border-bottom: 1px solid #ccc;
            font-size: 20px;
            color: #666666;
        }

        .menu_item:hover{
            color: white;
            background: lightseagreen;
        }
    </style>
</head>
<body>
<!-- 顶端导航栏 -->
<div class="pg_header">
    <!-- Logo与名称 -->
    <div class="logo left">
        <a href="javascript:void(0)" target="_blank">
            <img src="image/logo_1.png">
        </a>

    </div>

    <!-- 用户头像与菜单 -->
    <div class="person_info right" style="vertical-align: top;" >
        <img src="image/head.png">
        <span style="line-height: 50px;vertical-align: top;">小企鹅</span>
        <div class="info">
            <a href="javascript:void(0)">我的信息</a>
            <a href="javascript:void(0)">修改密码</a>
            <a href="javascript:void(0)">注销</a>
        </div>
    </div>
    <div class="icons right">
        <i class="far fa-bell"></i>
    </div>
</div>
<div class="pg_content">
    <!-- 左侧功能区菜单 -->
    <div class="menu">
        <a href = "/mgallery/management?method=list" class="menu_item" target="ifcontent">油画列表</a>
        <a href = "#" class="menu_item" target="ifcontent">新增作品</a>
    </div>
    <!-- 主体框架 -->
    <div class="content">
        <iframe name="ifcontent" style="width:100%;height:100%;overflow-y: hidden;border:0px;min-width: 800px;" src="/mgallery/management?method=list"></iframe>
    </div>
</div>
</body>
</html>

利用SweetAlert实现预览功能 [美观对话框]

正确使用 equals 方法避免产生空指针异常 - 简书 (jianshu.com)

list.jsp
<%@page contentType = "text/html;charset=utf-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>油画列表</title>
    <script src="js\jquery-3.4.1.min.js" type="text/javascript"></script>
    <script src="js\sweetalert2.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="css\list.css">
    <script type="text/javascript">
        //对话框显示预览 // 变成jquary对象 对自定义属性名进行提取
        function showPreview(previewObj){
            var preview = $(previewObj).attr("data-preview");
            var pname = $(previewObj).attr("data-pname");
            Swal.fire({
                title: pname,
                html : "<img src='" + preview + "' style='width:361px;height:240px'>",
                showCloseButton: true,
                showConfirmButton: false
            })
        }
    </script>
</head>
<body>
    <div class="container">
        <fieldset>
            <legend>油画列表</legend>
            <div style="height: 40px">
                <a href="#" class="btn-button">新增</a>
            </div>
            <!-- 油画列表 -->
            <table cellspacing="0px">
                <thead>
                    <tr style="width: 150px;">
                        <th style="width: 100px">分类</th>
                        <th style="width: 150px;">名称</th>
                        <th style="width: 100px;">价格</th>
                        <th style="width: 400px">描述</th>
                        <th style="width: 100px">操作</th>
                    </tr>
                </thead>
                <c:forEach items="${pageModel.pageData }" var="painting">
                    <tr>
                        <td>
                            <c:choose>
                                <c:when test="${painting.category==1 }">现实主义</c:when>
                                <c:when test="${painting.category==2 }">抽象主义</c:when>
                                <c:otherwise>未知的类型</c:otherwise>
                            </c:choose>
                        </td>
                        <td>${painting.pname }</td>
                        <td><fmt:formatNumber pattern="¥0.00" value="${painting.price }"></fmt:formatNumber> </td>
                        <td>${painting.description }</td>
                        <td> <!-- 自定义用 data-preview data-pname    this就是指向这个超链接的本身 -->
                            <a class="oplink" data-preview="${painting.preview}" data-pname = "${painting.pname}" href="javascript:void(0)" onclick="showPreview(this)">预览</a>
                            <a class="oplink" href="#">修改</a>
                            <a class="oplink" href="#">删除</a>
                        </td>
                    </tr>
                </c:forEach>
            </table>
            <!-- 分页组件 -->
            <ul class="page">
                <li><a href="/mgallery/management?method=list&p=1">首页</a></li>
                <li><a href="/mgallery/management?method=list&p=${pageModel.hasPreviousPage?pageModel.page-1:1}">上页</a></li>
                <c:forEach begin="1" end="${pageModel.totalPages }" step="1" var="pno">
                    <li ${pno==pageModel.page?"class='active'":""}>
                        <a href="/mgallery/management?method=list&p=${pno }">${pno }</a>
                    </li>
                </c:forEach>
                <li><a href="/mgallery/management?method=list&p=${pageModel.hasNextPage?pageModel.page+1:pageModel.totalPages}">下页</a></li>
                <li><a href="/mgallery/management?method=list&p=${pageModel.totalPages}">尾页</a></li>
            </ul>
        </fieldset>
    </div>

</body>
</html>

处理文件上传页面 [表单校验 文件上传 处理XML文件]

  • 利用Apache Commons FileUpload组件实现上传功能
  • 封装可重用的js脚本解决表单校验问题
  • 基于Dom4j对XML文件进行追加操作
create.jsp
<!-- 新增油画页面 -->
<%@page contentType = "text/html;charset=utf-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>新增油画</title>
<link rel="stylesheet" type="text/css" href="css\create.css">
<script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
</head>
<body>
    <div class="container">
        <fieldset>
            <legend>新增油画</legend>
            <form action="" method="post"
                autocomplete="off" enctype="multipart/form-data">
                <ul class="ulform">
                    <li>
                        <span>油画名称</span>
                        <span id="errPname"></span>
                        <input id="pname" name="pname" />
                    </li>
                    <li>
                        <span>油画类型</span>
                        <span id="errCategory"></span>
                        <select id="category" name="category">
                            <option value="-1">请选择油画类型</option>
                            <option value="1">现实主义</option>
                            <option value="2">抽象主义</option>
                        </select>
                    </li>
                    <li>
                        <span>油画价格</span>
                        <span id="errPrice"></span>
                        <input id="price" name="price"/>
                    </li>
                    <li>
                        <span>作品预览</span>
                        <span id="errPainting"></span>
                        <input id="painting" name="painting" type="file" 
                            style="padding-left: 0px;" accept="image/*" />
                    </li>

                    <li>
                        <span>详细描述</span>
                        <span id="errDescription"></span>
                        <textarea
                            id="description" name="description"></textarea>
                    </li>
                    <li style="text-align: center;">
                        <button type="submit" class="btn-button">提交表单</button>
                    </li>
                </ul>
            </form>
        </fieldset>
    </div>

</body>
</html>
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();

    public ManagementController() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        //通过method参数区分不同的操作
        String method = req.getParameter("method");
        if(Objects.equals(method,"list")) {//分页查询列表
            this.list(req,resp);
            //正确使用 equals 方法避免产生空指针异常https://www.jianshu.com/p/306de20dd228
        } else if (Objects.equals(method,"delete")) {
            //
        } else if (Objects.equals(method,"show_create")) {
            this.showCreatePage(req,resp); //带show的一定是跳转页面
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p == null) {
            p = "1";
        }
        if (r == null) {
            r = "6";
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req,resp); //跳转jsp
    }
    private void showCreatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); //请求转发
    }
}

文件上传必要前提条件

  • form表单method = “post 因为二进制文件不可以放在url中传递
  • form表单enctype = “multipart/form-data 允许保存二进制数据存放在请求体中发送到服务端
    enctype=”application/x-www-form-urlencoded 采用url编码的方式以字符串的形式将数据保存在请求体中发送到服务器
  • form表单持有file类型input进行文件选择

Apache Commons FileUpload [简化文件上传]

在java服务器端完成上传文件的处理
  • FileUpload组件提供了java文件上传底层支持

FileItem其本质就是对前台数据进行封装

ManagementController.java
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();

    public ManagementController() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        //通过method参数区分不同的操作
        String method = req.getParameter("method");
        if(Objects.equals(method,"list")) {//分页查询列表
            this.list(req,resp);
            //正确使用 equals 方法避免产生空指针异常https://www.jianshu.com/p/306de20dd228
        } else if (Objects.equals(method,"delete")) {
            //
        } else if (Objects.equals(method,"show_create")) {
            this.showCreatePage(req,resp); //带show的一定是跳转页面
        } else if (Objects.equals(method, "create")) {
            this.create(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p == null) {
            p = "1";
        }
        if (r == null) {
            r = "6";
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req,resp); //跳转jsp
    }
    //显示新增页面
    private void showCreatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); //请求转发
    }
    //新增油画方法
    private void create(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*文件上传时的数据处理与标准表单完全不同
        String pname = req.getParameter("pname"); form表单enctype = "multipart/form-data"运用后 无法得到字符串格式的数据
        System.out.println(pname);
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); 请求转发*/
        //1.初始化FileUpload组件 包含表单项的数据对象
        FileItemFactory factory = new DiskFileItemFactory();
        /**
         * FileItemFactory 用于将前端表单的数据转换为一个个FileItem对象
         * ServletFileUpload 是为FileUpload组件提供Java web的Http请求解析
         */
        ServletFileUpload sf = new ServletFileUpload(factory);
        //2.遍历所有FileItem
        try {
            List<FileItem> formData = sf.parseRequest(req);//将表单数据转换为FileItem对象 和前台输入项一一对应
            //区分哪个是普通对象 哪个是文件对象
            for (FileItem fi : formData){
                if (fi.isFormField() == true){//普通输入框
                    System.out.println("普通输入项:" + fi.getFieldName() + ":" + fi.getString("UTF-8"));
                }else { //文件上传框
                    System.out.println("文件上传项:" + fi.getFieldName()); //没有文本数值不用输出
                    //3.文件保存到服务器目录 已经确定了文件上传项
                    String path = req.getServletContext().getRealPath("/upload");
                    System.out.println("上传文件目录:" + path);
//                    String fileName = "test.jpg";
                    String fileName = UUID.randomUUID().toString();//随机生成文件名 根据计算机的特性 生成随机唯一字符串
                    //fi.getName()得到原始文件名, 截取最后一个"."后所有字符串,例如:wxml.jpg -> .jpg
                    String suffix = fi.getName().substring(fi.getName().lastIndexOf("."));
                    fi.write(new File(path, fileName + suffix)); //传入文件对象会自动的帮我们把客户端上传的文件传到服务器某个文件中
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Dom4j写操作开发流程

  • **SAXReader.read()**读取XML文件,得到Document对象
  • p=root,addElement(“painting”) - 创建新的节点
  • p.addElement(“pname”) - 创建新的子节点
  • document.write(writer) - 向XML写入新的节点
请求转发
请求转发是一种服务器端的行为,通过request对象来完成操作。当客户端发送请求以后,对应的Servlet会做出业务逻辑处理,然后调用forward()方法,将请求发送到服务器中另外的Servlet中去。

实现方法:
request.getRequestDispatcher("URL地址").forward(request,response);
响应重定向
响应重定向是一种客户端的行为,通过response对象来完成操作。当客户端第一次发出请求后,服务器中Servlet接收请求以后,通过调用sendRedirect()方法指定另一个Servlet,此时客户端会根据路径再次发出请求访问下一个Servlet,服务器再次接收请求后做出响应返回给客户端。

实现方法:
response.sendRedirect("URL地址")
请求转发与响应重定向的区别
1、请求转发以后浏览器URL地址栏不变,响应重定向以后浏览器URL地址栏发生改变
2、请求转发是服务器端行为,响应重定向是客户端行为
3、请求转发只做了一次访问请求,得到一次响应;响应重定向是做了两次请求,得到两次响应
4、请求转发是在服务器内部进行的,所以不可以跨域访问;响应重定向可以做到跨域访问
5、请求转发可以使用request作用域共享数据;响应重定向不可以使用request作用域,但是可以使用session域共享资源
XmlDataSource.java[新增了append追加操作]
package com.example.mgallery.utils;

import com.example.mgallery.entity.Painting;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

//用于将XML文件解析为Java对象
public class XmlDataSource {
    //通过static静态关键字保证数据全局唯一
    private static List data = new ArrayList(); //油画集合
    private static String dataFile;

    static { //程序运行以后去得到类路径目录下的/painting.xml的路径地址
        dataFile = XmlDataSource.class.getResource("/painting.xml").getPath();
        reload(); //在所有的写入操作以后都要写入
    }
    private static void reload(){
        //若得到特殊字符进行编码转换 空格 c:\new style\painting.xml
        try {
            URLDecoder.decode(dataFile, "UTF-8");
            System.out.println(dataFile);
            //利用Dom4j对XML进行解析 读取XML
            SAXReader reader = new SAXReader();
            //1.获取Document文档对象
            Document document = reader.read(dataFile);
            //2.Xpath得到XML节点集合 获取多个xml节点
            List<Node> nodes = document.selectNodes("/root/painting");
            data.clear(); //清空 在空的数据基础上重新添加
            for (Node node : nodes) {
                Element element = (Element) node;
                String id = element.attributeValue("id");//获得id
                String pname = element.elementText("pname");//获得子节点
                Painting painting = new Painting();
                painting.setId(Integer.parseInt(id));
                painting.setPname(pname);
                painting.setCategory(Integer.parseInt(element.elementText("category")));
                painting.setPrice(Integer.parseInt(element.elementText("price")));
                painting.setPreview(element.elementText("preview"));
                painting.setDescription(element.elementText("description"));
                data.add(painting);//将List data 保存油画的完整信息

                System.out.println(id + ";" + pname);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取所有油画Painting对象
     *
     * @return Painting List
     */
    public static List<Painting> getRawData() {
        return data;
    }

    //Dom4j实现XML追加操作
    public static void append(Painting painting) { //末尾追加新的油画
        //1.读取XML文档,得到Document对象
        SAXReader reader = new SAXReader();
        Writer writer = null;
        try {
            Document document = reader.read(dataFile);
            //2.创建新的painting节点
            Element root = document.getRootElement();//得到原始文档xml根节点 <root>
            Element p = root.addElement("painting");//创建一个新的子节点
            //3.创建painting节点的各个子节点
            p.addAttribute("id", String.valueOf(data.size() + 1)); //生成新的id对象
            p.addElement("pname").setText(painting.getPname()); //返回新的节点 设置其文本值
            p.addElement("category").setText(painting.getCategory().toString());
            p.addElement("price").setText(painting.getPrice().toString());
            p.addElement("preview").setText(painting.getPreview());
            p.addElement("description").setText(painting.getDescription());
            //4.写入XML,完成追加操作
            writer = new OutputStreamWriter(new FileOutputStream(dataFile), "UTF-8");
            document.write(writer); //向目标dataFile原始xml中进行新节点的更新
            System.out.println(dataFile);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) { //write有开就有关 已经被实例化
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace(); //遇到异常 打印堆栈
                }
            }
            //清空 在空的数据基础上重新添加 无论成功与否都会重新加载数据 如果照片没找到 就清空数据
            reload();
        }

    }

    public static void main(String[] args) {
//        new XmlDataSource();
//        List<Painting> ps = XmlDataSource.getRawData();
//        System.out.println(ps);
        Painting p = new Painting();
        p.setPname("油画测试");
        p.setCategory(1);
        p.setPrice(4000);
        p.setPreview("upload/10.jpg");
        p.setDescription("测试油画描述");
        XmlDataSource.append(p);
    }
}
ManagementController.java[新增了create方法]
package com.example.mgallery.controller;

import com.example.mgallery.entity.Painting;
import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();

    public ManagementController() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        //通过method参数区分不同的操作
        String method = req.getParameter("method");
        if(Objects.equals(method,"list")) {//分页查询列表
            this.list(req,resp);
            //正确使用 equals 方法避免产生空指针异常https://www.jianshu.com/p/306de20dd228
        } else if (Objects.equals(method,"delete")) {
            //
        } else if (Objects.equals(method,"show_create")) {
            this.showCreatePage(req,resp); //带show的一定是跳转页面
        } else if (Objects.equals(method, "create")) {
            this.create(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p == null) {
            p = "1";
        }
        if (r == null) {
            r = "6";
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req,resp); //跳转jsp
    }
    //显示新增页面
    private void showCreatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); //请求转发
    }
    //新增油画方法
    private void create(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*文件上传时的数据处理与标准表单完全不同
        String pname = req.getParameter("pname"); form表单enctype = "multipart/form-data"运用后 无法得到字符串格式的数据
        System.out.println(pname);
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); 请求转发*/
        //1.初始化FileUpload组件 包含表单项的数据对象
        FileItemFactory factory = new DiskFileItemFactory();
        /**
         * FileItemFactory 用于将前端表单的数据转换为一个个FileItem对象
         * ServletFileUpload 是为FileUpload组件提供Java web的Http请求解析
         */
        ServletFileUpload sf = new ServletFileUpload(factory);
        //2.遍历所有FileItem
        try {
            List<FileItem> formData = sf.parseRequest(req);//将表单数据转换为FileItem对象 和前台输入项一一对应
            Painting painting = new Painting();//进行封装
            //区分哪个是普通对象 哪个是文件对象
            for (FileItem fi : formData){
                if (fi.isFormField() == true){//普通输入框
                    System.out.println("普通输入项:" + fi.getFieldName() + ":" + fi.getString("UTF-8"));
                    switch (fi.getFieldName()) { //得到字段名
                        case "pname":
                            painting.setPname(fi.getString("UTF-8"));
                            break;
                        case "category":
                            painting.setCategory(Integer.parseInt(fi.getString("UTF-8")));
                            break;
                        case "price":
                            painting.setPrice(Integer.parseInt(fi.getString("UTF-8")));
                            break;
                        case "description":
                            painting.setDescription(fi.getString("UTF-8"));
                            break;
                        default:
                            break;
                    }
                }else { //文件上传框
                    System.out.println("文件上传项:" + fi.getFieldName()); //没有文本数值不用输出
                    //3.文件保存到服务器目录 已经确定了文件上传项
                    String path = req.getServletContext().getRealPath("/upload");
                    System.out.println("上传文件目录:" + path);
//                    String fileName = "test.jpg";
                    String fileName = UUID.randomUUID().toString();//随机生成文件名 根据计算机的特性 生成随机唯一字符串
                    //fi.getName()得到原始文件名, 截取最后一个"."后所有字符串,例如:wxml.jpg -> .jpg
                    String suffix = fi.getName().substring(fi.getName().lastIndexOf("."));
                    fi.write(new File(path, fileName + suffix)); //传入文件对象会自动的帮我们把客户端上传的文件传到服务器某个文件中
                    painting.setPreview("upload/" + fileName + suffix); //形成一个完整的可以访问的url地址
                }
            }
            paintingService.create(painting); //新增功能
            //若弹出另一个页面进行另外一个操作 新页面以后的后续操作 和前面的操作紧密联系的 用请求转发 当前请求給下一个功能继续操作
            resp.sendRedirect("/mgallery/management?method=list");//响应重定向跳转列表页继续相应的处理 跟前面的新增功能联系不那么紧密
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

开发表单校验JS脚本[可重用的表单校验]

  • 以独立的js脚本文件存储可被不同表单项重用的校验规则
  • 每一个校验逻辑均独立存储
  • 可自定义触发时机,例如:失去焦点、选择时、提交前…
实现思路
/*
检查是否为空 input表单域选择器    errSelector错误提示选择器    true-校验成功 false-校验失败
*/
function checkEmpty(input, errSelector){
    var val = $(input).val();
    if($.trim(val)==""){
        switchValid(false, input, errSelector, "请输入内容")
        return false;
    }else{
        switchValid(true, input, errSelector);
        return true;
    }
}
调用方法1 失去焦点的时候触发onblur
<span>详细描述</span>
<span id="errDescription"></span>
<textarea id="description" name="description" onblur="checkEmpty('#description','#errDescription')"></textarea>
调用方法2 在提交前组合校验
<script type="text/javascript">
    function checkSubmit(){ //提交前表单校验
        var result = true;
        var r1 = checkPname('#pname','#errPname',-1); //检查名称
        var r2 = checkCategory('#category','#errCategory'); //检查分类
        var r3 = checkPrice('#price','#errPrice'); //检查价格
        var r4 = checkFile('#painting', '#errPainting'); //检查上传文件
        var r5 = checkEmpty('#description', '#errDescription'); //检查描述
        if(r1 && r2 && r3 && r4 && r5){//所有检查通过允许提交表单
            return true;
        }else{
            return false;
        }
    }
</script>
validation.js 且在create.jsp中把引用的标签打上js状态 onblur/onchange
/**
 * 隐藏、显示错误信息
 * @param onOff true 验证成功, 隐藏错误     false 校验失败,显示错误
 * @param input 表单域选择器
 * @param errSelector 错误提示选择器
 * @param message 错误信息
 * @returns
 */
function switchValid(onOff, input, errSelector, message) {
    if (onOff == false) {
        $(errSelector).text(message);
        $(input).addClass("error_input"); //错误标红
        $(errSelector).addClass("error_message");
    } else {
        $(errSelector).text("");
        $(input).removeClass("error_input"); //错误标红
        $(errSelector).removeClass("error_message");
    }
}

/**
 * 检查是否为空
 * @param input 表单域选择器
 * @param errSelector 错误提示选择器
 * @returns true-校验成功 false-校验失败
 */
function checkEmpty(input, errSelector) {
    var val = $(input).val(); //获取当前选择的数值
    if ($.trim(val) == "") { //将字符串前后空格删掉
        switchValid(false, input, errSelector, "请输入内容"); //非空校验失败时显示错误
        return false;
    } else {
        switchValid(true, input, errSelector); //正确情况
        return true;
    }
}

function checkCategory(input, errSelector) {
    var val = $(input).val(); //获取当前选择的数值
    if (val == -1) {
        switchValid(false, input, errSelector, "请选择油画类型"); //非空校验失败时显示错误
        return false;
    } else {
        switchValid(true, input, errSelector); //正确情况
        return true;
    }
}

/**
 * 价格必须是整数
 * @param input 表单域选择器
 * @param errSelector 错误提示选择器
 * @returns true-校验成功 false-校验失败
 */
function checkPrice(input, errSelector) {
    var val = $(input).val(); //获取当前选择的数值
    var regex = /^[1-9][0-9]*$/   //利用正则表达式进行校验信息
    if (!regex.test(val)) {
        switchValid(false, input, errSelector, "无效的价格"); //非空校验失败时显示错误
        return false;
    } else {
        switchValid(true, input, errSelector); //正确情况
        return true;
    }
}

/**
 * 上传文件必须是图片
 * @param input 表单域选择器
 * @param errSelector 错误提示选择器
 * @returns true-校验成功 false-校验失败
 */
function checkFile(input, errSelector) {
    if (checkEmpty(input, errSelector) == false) {
        return false;
    }
    var val = $(input).val().toLowerCase()//小写转换 PNG png
    if (val.length < 4) { //x.xxxx
        switchValid(false, input, errSelector, "请选择有效的图片"); //非空校验失败时显示错误
        return false;
    }
    suffix = val.substring(val.length - 3); //拿到最后的扩展名
    if (suffix == "jpg" || suffix == "png" || suffix == "gif") {
        switchValid(true.input, errSelector);
        return true;
    }else {
        switchValid(false, input, errSelector, "请选择有效的图片");
        return false;
    }
}

实现新增油画功能[表单submit全验证]

添加一个全局验证
create.jsp
<!-- 新增油画页面 -->
<%@page contentType="text/html;charset=utf-8" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>新增油画</title>
    <link rel="stylesheet" type="text/css" href="css\create.css">
    <script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="js/validation.js"></script>
    <script type="text/javascript">
        function checkSubmit() {
            var result = true;
            var r1 = checkEmpty("#pname", "#errPname");
            var r2 = checkCategory('#category', '#errCategory');
            var r3 = checkPrice('#price', '#errPrice');
            var r4 = checkFile('#painting', '#errPainting');
            var r5 = checkEmpty('#description', '#errDescription');
            if (r1 && r2 && r3 && r4 && r5) {
                return true;
            }else {
                return false;
            }
        }
    </script>
</head>
<body>
<div class="container">
    <fieldset>
        <legend>新增油画</legend>
        <form action="/mgallery/management?method=create" method="post"
              autocomplete="off" enctype="multipart/form-data" onsubmit="return checkSubmit()">
            <ul class="ulform">
                <li>
                    <span>油画名称</span>
                    <span id="errPname"></span>
                    <input id="pname" name="pname" onblur="checkEmpty('#pname','#errPname')"> <!--在失去焦点时触发-->
                </li>
                <li>
                    <span>油画类型</span>
                    <span id="errCategory"></span>
                    <select id="category" name="category" onchange="checkCategory('#category','#errCategory')">
                        <option value="-1">请选择油画类型</option>
                        <option value="1">现实主义</option>
                        <option value="2">抽象主义</option>
                    </select>
                </li>
                <li>
                    <span>油画价格</span>
                    <span id="errPrice"></span>
                    <input id="price" name="price" onblur="checkPrice('#price','#errPrice')">
                </li>
                <li>
                    <span>作品预览</span>
                    <span id="errPainting"></span>
                    <input id="painting" name="painting" type="file"
                           style="padding-left: 0px;" accept="image/*"
                           onchange="checkFile('#painting','#errPainting')"/>
                    <%--    accept="image/*" 默认保留所有图片格式的文件--%>
                </li>

                <li>
                    <span>详细描述</span>
                    <span id="errDescription"></span>
                    <textarea
                            id="description" name="description"
                            onblur="checkEmpty('#description','#errDescription')"></textarea>
                </li>
                <li style="text-align: center;">
                    <button type="submit" class="btn-button">提交表单</button>
                </li>
            </ul>
        </form>
    </fieldset>
</div>

</body>
</html>
management.html  和  list.jsp 添加href超链接 
客户点击新增的时候可以跳转到新增页面 
<a href = "/mgallery/management?method=show_create"></a>

实现修改页表单数据回填

修改实现思路
  • 修改与新增的最大不同是在修改前要加载原有数据
  • 在修改页面放置hidden隐藏域保存id编号,随表单提交
  • 对XML更新时,先按id获取原始记录,在此基础上覆盖更新

Dao service

PaintingService.java
package com.example.mgallery.service;

import com.example.mgallery.dao.PaintingDao;
import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;

import java.util.List;
//完成业务逻辑 service与dao进行传递调用
public class PaintingService {
    private PaintingDao paintingDao = new PaintingDao();
    public PageModel pagination(int page, int rows, String...category){  //最后一个是添加 可选参数(可能/不一定出现一个或多个)
        if (rows == 0){
            throw new RuntimeException("无效的rows参数");
        }
        if (category.length==0 || category[0] == null){
        return paintingDao.pagination(page, rows); //调用并返回
        }else { //程序进行可选调用 两个不同路径 尽量不要去修改类结构
            return paintingDao.pagination(Integer.parseInt(category[0]), page, rows);
        }
    }

    public void create(Painting painting){
        paintingDao.create(painting);
    }
    // 按编号查询油画 id油画编号 return油画对象
    public Painting findById(Integer id){
        Painting p = paintingDao.findById(id);
        if (p==null){
            throw new RuntimeException("[id=]" + id + "]油画不存在");
        }
        return p;
    }

    public static void main(String[] args) {
        PaintingService paintingService = new PaintingService();
        PageModel pageModel = paintingService.pagination(2, 6);//每页显示六条
        List<Painting> paintingList = pageModel.getPageData();
        for (Painting painting : paintingList){
            System.out.println(painting.getPname());
        }
        System.out.println(pageModel.getPageStartRow() + ":" + pageModel.getPageEndRow());

    }
}
update.jsp
<%@page contentType="text/html;charset=utf-8" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!-- 修改油画页面 -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>作品更新</title>
    <link rel="stylesheet" type="text/css" href="css\create.css">
    <script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="js/validation.js"></script>
    <script type="text/javascript">
        <!-- 提交前表单校验 -->
        function checkSubmit() {
            var result = true;
            var r1 = checkEmpty("#pname", "#errPname");
            var r2 = checkCategory('#category', '#errCategory');
            var r3 = checkPrice('#price', '#errPrice');
            var r4 = checkFile('#painting', '#errPainting');
            var r5 = checkEmpty('#description', '#errDescription');
            if (r1 && r2 && r3 && r4 && r5) {
                return true;
            } else {
                return false;
            }
        }
        //整个html被解析完后执行代码
        $(function(){//前面的被html解释完才执行 后面的EL表达式jsp渲染在服务器端产生 油画类型默认产生
            $("#category").val(${painting.category})
        })
    </script>
</head>
<body>
<div class="container">
    <fieldset>
        <legend>作品名称</legend>
        <form action="[这里写更新URL]" method="post"
              autocomplete="off" enctype="multipart/form-data"
              onsubmit="return checkSubmit()">
            <ul class="ulform">
                <li>
                    <span>油画名称</span>
                    <span id="errPname"></span>
                    <input id="pname" name="pname" onblur="checkEmpty('#pname','#errPname')" value="${painting.pname}"/>
                </li>
                <li>
                    <span>油画类型</span>
                    <span id="errCategory"></span>
                    <select id="category" name="category" onchange="checkCategory('#category','#errCategory')"
                            value="${painting.category}">
                        <option value="-1">请选择油画类型</option>
                        <option value="1">现实主义</option>
                        <option value="2">抽象主义</option>
                    </select>
                </li>
                <li>
                    <span>油画价格</span>
                    <span id="errPrice"></span>
                    <input id="price" name="price" onblur="checkPrice('#price','#errPrice')" value="${painting.price}"/>
                    </li>
                    <li>
                        <span>作品预览</span>
                        <span id=" errPainting"></span><br/>
                    <img id="preview" src="${painting.preview}" style="width:361px;height:240px"/><br/>
                    <input id="painting" name="painting" type="file" style="padding-left:0px;" accept="image/*"/>
                </li>
                <li>
                    <span>详细描述</span>
                    <span id="errDescription"></span>
                    <textarea
                            id="description" name="description"
                            onblur="checkEmpty('#description','#errDescription')"
                    >
                        ${painting.description}
                    </textarea>
                </li>
                <li style="text-align: center;">
                    <button type="submit" class="btn-button">提交表单</button>
                </li>
            </ul>
        </form>
    </fieldset>
</div>

</body>
</html>
ManagmentController.java
package com.example.mgallery.controller;

import com.example.mgallery.entity.Painting;
import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();

    public ManagementController() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        //通过method参数区分不同的操作
        String method = req.getParameter("method");
        if(Objects.equals(method,"list")) {//分页查询列表
            this.list(req,resp);
            //正确使用 equals 方法避免产生空指针异常https://www.jianshu.com/p/306de20dd228
        } else if (Objects.equals(method,"delete")) {
            //
        } else if (Objects.equals(method,"show_create")) {
            this.showCreatePage(req,resp); //带show的一定是跳转页面
        } else if (Objects.equals(method, "create")) {
            this.create(req, resp);
        } else if (Objects.equals(method,"show_update")) {
            this.showUpdatePage(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p == null) {
            p = "1";
        }
        if (r == null) {
            r = "6";
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req,resp); //跳转jsp
    }
    //显示新增页面
    private void showCreatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); //请求转发
    }
    //新增油画方法
    private void create(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*文件上传时的数据处理与标准表单完全不同
        String pname = req.getParameter("pname"); form表单enctype = "multipart/form-data"运用后 无法得到字符串格式的数据
        System.out.println(pname);
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); 请求转发*/
        //1.初始化FileUpload组件 包含表单项的数据对象
        FileItemFactory factory = new DiskFileItemFactory();
        /**
         * FileItemFactory 用于将前端表单的数据转换为一个个FileItem对象
         * ServletFileUpload 是为FileUpload组件提供Java web的Http请求解析
         */
        ServletFileUpload sf = new ServletFileUpload(factory);
        //2.遍历所有FileItem
        try {
            List<FileItem> formData = sf.parseRequest(req);//将表单数据转换为FileItem对象 和前台输入项一一对应
            Painting painting = new Painting();//进行封装
            //区分哪个是普通对象 哪个是文件对象
            for (FileItem fi : formData){
                if (fi.isFormField() == true){//普通输入框
                    System.out.println("普通输入项:" + fi.getFieldName() + ":" + fi.getString("UTF-8"));
                    switch (fi.getFieldName()) { //得到字段名
                        case "pname":
                            painting.setPname(fi.getString("UTF-8"));
                            break;
                        case "category":
                            painting.setCategory(Integer.parseInt(fi.getString("UTF-8")));
                            break;
                        case "price":
                            painting.setPrice(Integer.parseInt(fi.getString("UTF-8")));
                            break;
                        case "description":
                            painting.setDescription(fi.getString("UTF-8"));
                            break;
                        default:
                            break;
                    }
                }else { //文件上传框
                    System.out.println("文件上传项:" + fi.getFieldName()); //没有文本数值不用输出
                    //3.文件保存到服务器目录 已经确定了文件上传项
                    String path = req.getServletContext().getRealPath("/upload");
                    System.out.println("上传文件目录:" + path);
//                    String fileName = "test.jpg";
                    String fileName = UUID.randomUUID().toString();//随机生成文件名 根据计算机的特性 生成随机唯一字符串
                    //fi.getName()得到原始文件名, 截取最后一个"."后所有字符串,例如:wxml.jpg -> .jpg
                    String suffix = fi.getName().substring(fi.getName().lastIndexOf("."));
                    fi.write(new File(path, fileName + suffix)); //传入文件对象会自动的帮我们把客户端上传的文件传到服务器某个文件中
                    painting.setPreview("upload/" + fileName + suffix); //形成一个完整的可以访问的url地址
                }
            }
            paintingService.create(painting); //新增功能
            //若弹出另一个页面进行另外一个操作 新页面以后的后续操作 和前面的操作紧密联系的 用请求转发 当前请求給下一个功能继续操作
            resp.sendRedirect("/mgallery/management?method=list");//响应重定向跳转列表页继续相应的处理 跟前面的新增功能联系不那么紧密
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //显示更新页面
    private void showUpdatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String id = req.getParameter("id");//前台传来的id号
        Painting painting = paintingService.findById(Integer.parseInt(id));
        req.setAttribute("painting", painting); //将得到的放入其中
        req.getRequestDispatcher("/WEB-INF/jsp/update.jsp").forward(req,resp);
    }
}
list.jsp
<ul class="page">
                <li><a href="/mgallery/management?method=list&p=1">首页</a></li>
                <li><a href="/mgallery/management?method=list&p=${pageModel.hasPreviousPage?pageModel.page-1:1}">上页</a></li>
                <c:forEach begin="1" end="${pageModel.totalPages }" step="1" var="pno">
                    <li ${pno==pageModel.page?"class='active'":""}>
                        <a href="/mgallery/management?method=list&p=${pno }">${pno }</a>
                    </li>
                </c:forEach>
                <li><a href="/mgallery/management?method=list&p=${pageModel.hasNextPage?pageModel.page+1:pageModel.totalPages}">下页</a></li>
                <li><a href="/mgallery/management?method=list&p=${pageModel.totalPages}">尾页</a></li>
            </ul>

利用Dom4j对XML进行更新

XmlDataSource.java
package com.example.mgallery.utils;

import com.example.mgallery.entity.Painting;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

//用于将XML文件解析为Java对象
public class XmlDataSource {
    //通过static静态关键字保证数据全局唯一
    private static List data = new ArrayList(); //油画集合
    private static String dataFile;

    static { //程序运行以后去得到类路径目录下的/painting.xml的路径地址
        dataFile = XmlDataSource.class.getResource("/painting.xml").getPath();
        reload(); //在所有的写入操作以后都要写入
    }
    private static void reload(){
        //若得到特殊字符进行编码转换 空格 c:\new style\painting.xml
        try {
            URLDecoder.decode(dataFile, "UTF-8");
            System.out.println(dataFile);
            //利用Dom4j对XML进行解析 读取XML
            SAXReader reader = new SAXReader();
            //1.获取Document文档对象
            Document document = reader.read(dataFile);
            //2.Xpath得到XML节点集合 获取多个xml节点
            List<Node> nodes = document.selectNodes("/root/painting");
            data.clear(); //清空 在空的数据基础上重新添加
            for (Node node : nodes) {
                Element element = (Element) node;
                String id = element.attributeValue("id");//获得id
                String pname = element.elementText("pname");//获得子节点
                Painting painting = new Painting();
                painting.setId(Integer.parseInt(id));
                painting.setPname(pname);
                painting.setCategory(Integer.parseInt(element.elementText("category")));
                painting.setPrice(Integer.parseInt(element.elementText("price")));
                painting.setPreview(element.elementText("preview"));
                painting.setDescription(element.elementText("description"));
                data.add(painting);//将List data 保存油画的完整信息

                System.out.println(id + ";" + pname);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取所有油画Painting对象
     *
     * @return Painting List
     */
    public static List<Painting> getRawData() {
        return data;
    }

    //Dom4j实现XML追加操作
    public static void append(Painting painting) { //末尾追加新的油画
        //1.读取XML文档,得到Document对象
        SAXReader reader = new SAXReader();
        Writer writer = null;
        try {
            Document document = reader.read(dataFile);
            //2.创建新的painting节点
            Element root = document.getRootElement();//得到原始文档xml根节点 <root>
            Element p = root.addElement("painting");//创建一个新的子节点
            //3.创建painting节点的各个子节点
            p.addAttribute("id", String.valueOf(data.size() + 1)); //生成新的id对象
            p.addElement("pname").setText(painting.getPname()); //返回新的节点 设置其文本值
            p.addElement("category").setText(painting.getCategory().toString());
            p.addElement("price").setText(painting.getPrice().toString());
            p.addElement("preview").setText(painting.getPreview());
            p.addElement("description").setText(painting.getDescription());
            //4.写入XML,完成追加操作
            writer = new OutputStreamWriter(new FileOutputStream(dataFile), "UTF-8");
            document.write(writer); //向目标dataFile原始xml中进行新节点的更新
            System.out.println(dataFile);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) { //write有开就有关 已经被实例化
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace(); //遇到异常 打印堆栈
                }
            }
            //清空 在空的数据基础上重新添加 无论成功与否都会重新加载数据 如果照片没找到 就清空数据
            reload();
        }

    }

    public static void main(String[] args) {
//        new XmlDataSource();
//        List<Painting> ps = XmlDataSource.getRawData();
//        System.out.println(ps);
        Painting p = new Painting();
        p.setPname("油画测试");
        p.setCategory(1);
        p.setPrice(4000);
        p.setPreview("upload/10.jpg");
        p.setDescription("测试油画描述");
        XmlDataSource.append(p);
    }

    /**
     * 更新对应id的XML油画数据
     * @param painting 要更新的油画数据
     * @throws IOException
     */
    public static void update(Painting painting){
        SAXReader reader = new SAXReader();
        Writer writer = null;
        try {
            Document document = reader.read(dataFile);
            //节点路径[@属性名=属性值]
            // /root/paintin[@id=x]  根节点
            List<Node> nodes = document.selectNodes("/root/painting[@id=" + painting.getId() + "]");
            if (nodes.size() == 0){
                throw new RuntimeException("id=" + painting.getId() + "编号油画不存在");
            }
            Element p = (Element) nodes.get(0); //唯一的节点提取出来
            p.selectSingleNode("pname").setText(painting.getPname()); //得到指定标签名的唯一节点 指定油画更新id操作
            p.selectSingleNode("category").setText(painting.getCategory().toString());
            p.selectSingleNode("price").setText(painting.getPrice().toString());
            p.selectSingleNode("preview").setText(painting.getPreview());
            p.selectSingleNode("description").setText(painting.getDescription());
            writer = new OutputStreamWriter(new FileOutputStream(dataFile),"UTF-8");
            document.write(writer);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (writer != null){
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            reload(); //对原始集合进行重载更新
        }
    }

    /**
     * 按id号删除XML油画数据
     * @param id 油画id
     * @throws IOException
     */

    public static void delete(Integer id) {
        SAXReader reader = new SAXReader();
        Writer writer = null;
        try {
            Document document = reader.read(dataFile);
            List<Node> nodes = document.selectNodes("/root/painting[@id=" + id + "]");
            if(nodes.size() == 0) {
                throw new RuntimeException("id=" + id + "编号油画不存在");
            }
            Element p = (Element)nodes.get(0);
            p.getParent().remove(p);
            writer = new OutputStreamWriter(new FileOutputStream(dataFile),"UTF-8");
            document.write(writer);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(writer!=null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            reload();
        }
    }
}


项目总结

CRUD增删改查

工程结构
mgallery - eclipse工程项目
 /src - java源代码目录
 /WebContent - Web资源目录
 /css - css文件目录
 /js - js文件目录
 /image - 图片资源目录
 /upload - 上传文件目录
 /WEB-INF   //jsp数据来自controller 不允许在web中直接访问 要从控制器跳转
   /jsp - jsp页面目录
   /lib - jar文件目录
   /classes - 编译后的class目录
   /web.xml web描述符文件
包结构[src目录下 根据MVC进行结构划分]
com.imooc.mgallery //逆命名法
    /controller - 存放Servlet控制器类 //承上启下接收参数 调用逻辑 返回处理结果
    /service - 存放处理逻辑类model //完成业务逻辑 service与dao进行传递调用
    /dao - Data Access Object 数据访问对象类 数据读写的java类 数据来自xml文件
    /entity - 存放实体类 JavaBean java中的简单对象
    /utils - 通用工具类 底层通用的工具类或方法

Dao类[Data Access Object]

  • XxxDao类只负责对数据进行读取、写入操作
  • 只负责对数据 增、删、改、查
示例:PaintingDao //针对油画数据进行增删改查
public class PaintingDao{
    public void append(){...} //新增数据
    public void update(){...} //修改数据
    public void delete(){...} //删除数据
    public void findAll(){...} //查询数据
}

Service与Dao的关系

  • Service负责进行流程处理,需**持久化[java处理在内存中 存储在数据库防止丢失]**时调用Dao
  • Dao只负责单纯对数据进行增删改查操作
  • Service允许单向调用Dao,反向不允许

JavaBean

  • 对一种类的使用形式的统称

  • JavaBean是一种Java中可重用组件

  • JavaBean不是技术,而是一种Java类的格式要求

  • JavaBean在Java领域非常常见,通常用于存储数据

JavaBean格式要求

  • 类必须是pubilc并提供默认构造函数
  • 所有属性private私有化
  • 属性通过getter与setter方法进行读写
public class Painting{//类公有化
    public Painting(){...}; //提供默认构造函数,可不写
    private Integer id; //属性私有
    private String pname;
    public Integer getId(){return id;} //getter获取属性值
    public void setId(Integer id){this.id = id;} //setter设置属性值
    public String getPname(){return pname;}
    public void setPname(String pname){this.pname = pname;}
}

创建mgallery工程 实现思路

  • 开发PaintingDao读取XML数据,实现分页操作
  • 开发PaintingService服务类,对PaintingDao进行调用
  • 开发PaintingController控制器,调用PaintingService
  • 重写index.html,利用JSP技术读取分页数据

关键类与方法[对于前台来说]

  • XmlDataSource类[全局有且只有一份数据保存在内存中] - XML数据源工具类,简化Dao提取操作
  • PaintingDao.pagination() - 数据分页查询方法
  • PageModel类 - 分页结果的包装类 {分页处理的核心对象}

Dom4j

  • Dom4j是一个易用的、开源的库,用于解析XML。它应用于Java平台
  • Dom4j将XML视为Document对象
  • XML标签被Dom4j定义为Element对象

Dom4j开发流程回顾

  • SAXReader.read()读取XML文件,得到Document对象
  • document.selectNodes()利用Xpath得到XML节点集合
  • 遍历XML节点,包装成JavaBean或者集合对象返回

开发XmlDataSource (utils)

油画数据分页设计思路

​ [视图 控制器 模型]

浏览器会发送请求查询指定的数据 PaintingController得到分页信息以后 再调用PaintingService 再调用PaintingDao 首先从PaintingDao调用XmlDataSource得到xml所有数据集合传入到PageModel进行分页 再返回PaintingDao-PaintingService-PaintingController放在当前的请求中,将请求跳转至index.jsp对数据进行渲染返回浏览器。

前台系统与后台系统区别

前台系统 后台系统
开放度 对外开放 不对外开放
面向群体 客户 企业内部人员
功能 提供查询与交互 管理前台数据
设计侧重点 良好的用户体验 严密的业务逻辑
访问量
典型案例 猫眼电影网 [后台有评论审核系统] XX公司无纸质化办公OA系统
后台实现功能

油画列表 油画预览 删除油画 修改油画 新增与上传油画

SweetAlert对话框

替代了传统的alert函数 有精心设计的对话框

新增功能实现难点

表单校验、文件上传、处理XML文件

文件上传:Apache Commons FileUpload

文件上传必要前提条件

文件上传必要前提条件

  • form表单method = “post 因为二进制文件不可以放在url中传递
  • form表单enctype = “multipart/form-data 允许保存二进制数据存放在请求体中发送到服务端
    enctype=”application/x-www-form-urlencoded 采用url编码的方式以字符串的形式将数据保存在请求体中发送到服务器
  • form表单持有file类型input进行文件选择

//1.初始化FileUpload组件 包含表单项的数据对象 每个输入项解析成FileItem
//2.ServletFileUpload* *是为FileUpload组件提供Java webHttp请求解析 将factory传入
//3.遍历所有FileItem 判断各种框 isFormField(true/false)来走是普通框还是文件框 文件框先把文件保存到服务器 UUID使文件名不重复

可重用的表单js

封装成JavaScript函数 在validation.js中呈现 可以自定义触发条件 别忘了还有个全局触发条件function写上面

实现新增油画功能

在xml中执行写入操作 利用Dom4j写操作开发
  • SAXReader.read()读取XML文件,得到Document对象
  • p=root.addElement(“painting”) - 创建新的节点
  • p.addElement(“pname”) - 创建新的子节点
  • document.write(write) - 向XML写入新的节点

实现修改与删除功能

所有更新逻辑都是在原始数据基础上覆盖更新,通过id号得到原始的旧数据,对旧的对象进行update

客户端采用Ajax方式提交Http请求
Controller方法处理后不再跳转任何jsp,而是通过响应输出JSON格式字符串
Tips:作为Ajax与服务器交互后,得到的不是整页HTML,而是服务器处理后的数据

阅读全文

JSON,JQuery,Ajax,Freemarker,百度Echarts,正则表达,过滤器,监听与实践

2023/9/20

JSON (JavaScript Object Notation) [JavaScript对象表示法]

  • 掌握JSON语法的书写规则
  • 掌握JSON与JavaScript的交互技巧
  • 掌握JSON与Java之间的序列化与反序列化

JSON是轻量级的文本数据交换格式,独立于语言,具有自我描述性,更易理解,已经逐渐替代了XML

{
    "sites":[
        {"name":"慕课网","url":"www.imooc.com"},
        {"name":"百度","url":"www.baidu.com"}
    ]
}

JSON语法规则

  • 数据由键(key)/值(value)描述,由逗号分隔
  • 大括号代表一个完整的对象,拥有多个键/值对
  • 中括号保存数组,多个对象之间使用逗号分割

所有的key和value都要用双引号进行标注

JSON存储员工信息

[
  {
    "empno": 7369,
    "ename": "李宁",
    "job": "软件工程师",
    "hiredate": "2017-05-12",
    "salary": 13000,
    "dname": "研发部"
  },
  {
    "empno": 8848,
    "ename": "小明",
    "job": "销售总监",
    "hiredate": "2022-4-23",
    "salary": 8000,
    "dname": "人事部",
    "customers": [
      {
        "cname": "李东"
      },
      {
        "cname": "刘楠"
      }
    ]
  }
]
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript">
        var json = [
            {
                "empno": 7369,
                "ename": "李宁",
                "job": "软件工程师",
                "hiredate": "2017-05-12",
                "salary": 13000,
                "dname": "研发部"
            },
            {
                "empno": 8848,
                "ename": "小明",
                "job": "销售总监",
                "hiredate": "2022-4-23",
                "salary": 8000,
                "dname": "人事部",
                "customers": [
                    {
                        "cname": "李东"
                    },
                    {
                        "cname": "刘楠"
                    }
                ]
            }
        ];
        //在浏览器控制台中对json内容进行输出
        console.log(json);
        for (var i = 0; i < json.length; i++){
            var emp = json[i];
            document.write("<h1>");
            document.write(emp.empno);
            document.write("," + emp.ename);
            document.write("," + emp.job);
            document.write("," + emp.hiredate);
            document.write("," + emp.salary);
            document.write("," + emp.dname);
            document.write("</h1>");
            if (emp.customers != null){
                document.write("<h2>"+emp.ename+"的顾客是")
                for (var j = 0; j < emp.customers.length; j++){
                    var customer = emp.customers[j];
                    document.write(customer.cname + ",");
                }
                document.write("</h2>");
            }
        }
    </script>
</head>
<body>

</body>
</html>

JSON与字符串相互转换

  • JSON.parse()方法字符串转换为JSON对象
  • JSON.stringify()方法JSON对象转换为字符串
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript">
       var str = "{\"class_name\" : \"五年fdhgfs级三班\"}"; <!--字符串 斜杠进行原意的输出-->
       var json = JSON.parse(str);
       console.log(str);
       console.log(json);
       document.write("班级:" + json.class_name);
    </script>
</head>
<body>

</body>
</html>

JS中JSON转为字符串

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript">
       var json1 = "{\"class_name\" : \"五年级三班\"}"; <!--字符串 斜杠进行原意的输出-->
       var str1 = JSON.stringify(json1);
       console.log(json1);
       console.log(str1);
       var json2 = {};
       json2.class_name = "五年级五班";
       json2.floor = "团委楼四层";
       json2.teacher = "王义夫"
       console.info(json2);
    </script>
</head>
<body>

</body>
</html>

JSON与Java交互

JSON工具包:json与java之间的互相转换

  • FastJson是阿里巴巴著名的JSON序列化与反序列化工具包
  • FastJson国内拥有大量使用者,拥有API简单,效率高等优点
https://repo1.maven.org/maven2/com/alibaba/fastjson/ 下载fastjson
或者配置maven依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>x.x.x</version>
</dependency>

FastJson序列化与JSON注解

public class Employee{
    private Integer empno;
    private String ename;
    private String job;
    //日期格式化
    @JSONField(name="hiredate", format="yyyy-MM-dd HH:mm:ss")
    private Date hdate;
    private Float salary;
    @JSONField(serialize = false) //不对dname进行序列化
    private String dname;
    ......
}
public class FastJsonSample1{
    public static void main(String[] args){
        Employee employee = new Employee();
        employee.setEmpno(4488);
        employee.setEname("王晓东");
        employee.setJob("客户经理");
        employee.setSalary(1000f);
        employee.setDname("市场部");
        //用日期来对时间进行初始化
        --------------//对应上方日期格式化@JSONField-------------------
        Calendar c = Calendar.getInstance();
        c.set(2019,0,30,0,0,0);
        employee.setHdate(c.getTime());//获取所需要的日期对象
        -------------------------------------------------------------
        //FastJSON中提供了JSON对象,完成对象对JSON字符串的转换
        String json = JSON.toJSONString(employee);
        System.out.println(json);
        //                            ↓↓ 原始字符串 反序列化类 ↓↓
        Employee emp = JSON.parseObject(json, Employee.class); //转换为何种对象
        //JSNO.parse()方法将字符串转换为JSON对象
        System.out.println(emp.getEname());
    }
}

FastJSON对象数组序列化与反序列化

JSON序列化与反序列化的用途:数据传输和数据保存 [A电脑中有100个员工信息,通过JSON把数据变成字符串发送給另一个系统,再进行转换]

public class FastJsonSample2{
    public static void main(String[] args){
        List emplist = new ArrayList();
        for(int i = 1; i <= 100; i++){
            Employee employee = new Employee();
            employee.setEmpno(4488 + i);
            employee.setEname("员工" + i);
            emplist.add(employee);
        }
        String json = JSON.toJSONString(emplist);
        System.out.println(json);
        List<Employee> emps = JSON.parseArray(json, Employee.class);
        for(Employee e : emps){
            System.out.println(e.getEmpno() + ":" + e.getEname());
        }
    }
}

JSON教程 https://www.cainiaojc.com/json/json_objects.html
JSON用于与Web服务器交换数据。数据发送到Web服务器时,数据必须是字符串

JSON格式几乎与JavaScript对象相同 
在JSON中,键必须是字符串,并用双引号引起来 {"name":"Seagull"}
在JavaScript中,键可以是字符串数字或标识符 {name : "Seagull"} 
JSON字符串类型 {"name":"Seagull"}
JSON数字类型 {"age":22}
JSON布尔类型 {"isAdult":true}
JSON对象类型 {"user":{"name:""Seagull","age":22,"city":"New Delhi"}} 
JSON数组类型 {"user":["Seagull","Cynthia","Tarush"]} 
//来自服务器的JSON
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
   if (this.readyState = 4 && this.status = 200) {
      var myObj = JSON.parse(this.responseText);
      document.getElementById("output").innerHTML = myObj.name;
   }
};
httpRequest.open("GET", "demo.json", true);
httpRequest.send();

**JSON.parse()**方法解析JSON字符串来构造JavaScript值或该字符串描述
**JSON.stringify()**方法将JavaScript对象或值转换为JSON字符串

解析日期parse()
<!DOCTYPE html>
<html>
<title>JSON.parse() 日期与json转换示例 - 基础教程(cainiaojc.com)</title>
<body>
<p>如果需要包含日期,则将其写为字符串,然后稍后将其转换回日期对象:</p>
<p id="output"></p>
<script>
var myJSON = '{"name":"Seagull", "birth":"1997-11-10", "city":"New Delhi"}';
var myObj = JSON.parse(myJSON);
myObj.birth = new Date(myObj.birth);
document.getElementById("output").innerHTML = myObj.name + " DOB is, " + myObj.birth;
</script>
</body>
</html> 
/*如果需要包含日期,则将其写为字符串,然后稍后将其转换回日期对象:
Seagull DOB is, Mon Nov 10 1997 08:00:00 GMT+0800 (中国标准时间)*/ 
解析日期stringify()
<!DOCTYPE html>
<html>
<title>JSON.stringify() 将日期对象转换为字符串示例 - 基础教程(cainiaojc.com)</title>
<body>
<p> JSON.stringify()方法会将任何日期对象转换为字符串:</p>
<p id="output"></p>
<script>
var myObj = { name: "Seagull", today: new Date(), city : "New Delhi" };
var myJSON = JSON.stringify(myObj);
document.getElementById("output").innerHTML = myJSON;
</script>
</body>
</html>
/*JSON.stringify()方法会将任何日期对象转换为字符串:
{"name":"Seagull","today":"2023-10-19T09:21:17.353Z","city":"New Delhi"*/

Java中的this.对象不会被函数内部修改 去寻找定义的值
this可以去调到外面的东西 解决了变量名一样的冲突
this区分成员变量和局部变量
this调用成员方法

Java中的static是公共的变量池


jQuery[主流的JavaScript库]与Ajax

  • 了解jQuery3的基本使用方法
  • 掌握Ajax处理流程与实现流程
  • 掌握jQuery中Ajax方法的使用

jQuery是一个轻量级JS库,使用十分简单;jQuery的核心是选择器用于获取页面元素

jQuery选择器 [先引用]

//引用jQuery
<script type="text/javascript">
    $("span").css("color","red");
    //简化形式 中间加JSON表达式
    $("a").css({"color" : "red", "font-size" : "30px", "font-weight" : "bold", "font-style" : "italic"})
    //为选中的选择器增添类效果 为li增加两个css类
    $("li").addClass("highlight myclass");
    $("p").removeClass("myclass") //移除类
    var color = $("a").css("color","orange");
    // alert(color)



### 设置元素内容

- **val()** 获取或设置输入项的值
- **text()** 获取或设置元素的纯文本
- **html()** 获取或设置元素内部的HTML

```html
.. <input type = "text" name = "uname" value = "admin"/> ..//将初始uname值变admin..
.. <span class = "myclass">我是myclass类的span标签</span> ..
<script type="text/javascript" src="js/jquery-3.1.1.js" ></script>
<script type = "text/javascript">
    //获取或设置文本输入框的内容
    $("input[name='uname']").val("administrator");
    
    //text与html方法最大的区别在于对文本中的html标签是否进行转义
    var vspan = $("span.myclass").text("<b>锄禾日当午</b>");
    alert(vspan);//锄禾日当午 纯文本中带了<b>标签
    
    var vspan = $("span.myclass").html("<b>锄禾日当午</b>");  
    alert(vspan);//<b>锄禾日当午</b>
</script>

jquery事件处理方法

  • on(“click”, function) 为选中的页面元素绑定单机事件
  • click(function) 是绑定事件的简写形式
  • 处理方法中提供了event参数包含了事件的相关信息
鼠标事件 键盘事件 表单事件 文档/窗口事件
click keypress
(键盘完整按下弹起)
submit
(表单提交)
load
(文档加载)
dblclick(双击) keydown
(键盘按下)
change
(表单发生变化)
resize
(文档窗口产生变化)
mouseenter
(滑鼠移动)
keyup
(键盘弹起)
focus
(表单输入项获得焦点)
scroll
(文档窗口滚动变化)
mouseleave
(滑鼠移出)
blur
(表单输入项失去焦点)
unload
(文档窗口关闭/卸载)
mouseover
(滑鼠移动过程)
.. <p class = "myclass">我是拥有myclass的p标签</p> ..
.. <input type = "text" name = "uname" value = "admin"/> ..
<script type = "text/javascript" src = "js/jquery-3.3.1.js"></script>
<script type = "text/javascript">
    $("p.myclass").on("click", function(){
    //$(this)是指当前事件产生的对象$("p.myclass")
    $(this).css("background-color", "yellow");
})
    $("span.myclass").click(function(){
    $(this).css("background-color", "lightgreen");                      
}) 
    
    $("input[name='uname']").keypress(function(event){
        console.log(event); //打印事件 F12 每次输入按键的Console
        $(this).css("color", "red");
    })
    
</script>
----------------------------------------------------------------
<!DOCTYPE html >
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn,staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
<script>
$(document).ready(function(){
 $("p").click(function()){
    <!--动作触发后执行的代码-->
    #(this).hide();
});
});
</script>
    </head>
    <body>
        <p>
            如果你点我,我就会消失。
        </p>
        <p>
            点我消失!
        </p>
    </body>

jQuery hide()和show()

使用hide()和show()方法来隐藏和显示HTML元素

$("#hide").click(function(){
    $("p").hide();
});
$("#show").click(function(){
    $("p").show();
});
隐藏速度

$(selector).hide(speed,callback);

$(selector).show(speed,callback);

<!DOCTYPE html >
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn,staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
<script>
$(document).ready(function(){
$("button").click(function(){
    $("p").hide(1000);
 });
});
</script>
    </head>
    <body>
      <button>隐藏</button>
      <p> 这是个段落</p>
      <p> 小段落</p>
    </body>

jQuery选择器实验室

sample2.html
<!DOCTYPE html >
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jQuery实验室</title>

<style>
.myclass {
    font-style: italic;
    color: darkblue;
}
/* 高亮css类 */
.highlight {
    color: red;
    font-size: 30px;
    background: lightblue;
}
</style>

</head>

<body>
    <div class="section">
        <h2>jQuery选择器实验室</h2>
        <input style="height: 24px" id="txtSelector" />
        <button id="btnSelect" style="height: 30px">选择</button>
        <hr />
        <div>
            <p id="welcome">欢迎来到选择器实验室</p>
            <ul>
                <li>搜索引擎:<a href="http://www.baidu.com">百度</a> <span> <a
                        style="color: darkgreen" href="http://www.so.com">360</a>
                </span>
                </li>
                <li>电子邮箱:<a href="http://mail.163.com">网易邮箱</a> <span> <a
                        style="color: darkgreen" href="http://mail.qq.com">QQ邮箱</a>
                </span>
                </li>
                <li>中国名校:<a href="http://www.tsinghua.edu.cn">清华大学</a> <span>
                        <a style="color: darkgreen" href="https://www.pku.edu.cn/">北京大学</a>
                </span>
                </li>
            </ul>

            <span class="myclass ">我是拥有myclass类的span标签</span>

            <p class="myclass">我是拥有myclass的p标签</p>
            <form id="info" action="#" method="get">
                <div>
                    用户名:<input type="text" name="uname" value="admin" /> 密码:<input
                        type="password" name="upsd" value="123456" />
                </div>
                <div>
                    婚姻状况: <select id="marital_status">
                        <option value="1">未婚</option>
                        <option value="2">已婚</option>
                        <option value="3">离异</option>
                        <option value="4">丧偶</option>
                    </select>
                </div>
                <div class="left clear-left">
                    <input type="submit" value="提交" /> <input type="reset" value="重置" />
                </div>
            </form>
        </div>
    </div>
    <script type="text/javascript" src="js/jquery-3.1.1.js" ></script>
    <script type="text/javascript">
        /*
            id选择器使用"#id值"进行选择
            css选择器使用".css类名"进行选择
            $(".myclass").addClass("highlight");
        */
        document.getElementById("btnSelect").onclick = function(){
            var selector = document.getElementById("txtSelector").value;
            //jquery选择器方法 选择器表达式
            $("*").removeClass("highlight") //在增加高亮之前 在当前页面将所有的类移除
            $(selector).addClass("highlight"); //对前面所选中的元素追加css类
        }
    </script>
</body>
</html>

Ajax(Asynchronous JavaScript And XML)介绍

Ajax(异步的 JavaScriptXML)

  • Ajax可以**在不刷新页面的前提下,进行页面布局更新,与后台交互**
    [不对整个页面刷新 只对局部数据更新刷新]
  • Ajax不是新技术,不是W3C的标准

Ajax的使用流程

  • 创建XmlHttpRequest对象[用于后台与服务器交换数据 是Ajax的核心]
  • 发送Ajax请求
  • 处理服务器响应
创建XmlHttpRequest对象
// 1.创建XmlHttpRequest对象
     var xmlhttp;
      if(windows.XMLHttpRequest){
       xmlhttp = new XMLHttpRequest();
    }else{
       xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
发送Ajax请求
  • xmlhttp.open() 用于创建请求
//创建请求
xmlhttp.open("Get","http://localhost/test?name=admin",true);
//发送到服务器
xmlhttp.send();
处理服务器响应
  • xmlhttp.onreadystatechange() 事件用于监听Ajax的执行过程
  • xmlhttp.readyState = “number 属性说明XMLHttpRequest当前状态
  • xmlhttp.status 属性服务器响应状态码,200:成功 404:未找到
readyState指 说明
readyState = 0 请求未初始化
readyState = 1 服务器连接已建立
readyState = 2 请求已被接收
readyState = 3 请求正在处理
readyState = 4 响应文本已被接收
完整编写
<body>
    <input id="btnLoad" type="button" value="加载">
    <div id="divContent"></div>
    <script>
        document.getElementById("btnLoad").onclick = function(){
            //1.创建XmlHttpRequest对象
            var xmlhttp;
            if(windows.XMLHttpRequest){
                xmlhttp = new XMLHttpRequest();
            }else{
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            console.log(xmlhttp); //在控制台console里有数据
            //2.发送Ajax请求 //F12中Network content有Response返回
            xmlhttp.open("GET", "/json_war_exploded/news.html", true)
            //3.处理服务器响应
            xmlhttp.onreadystatechange = function(){
            //响应已被接收且服务器处理成功时才执行
                if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
                //获取响应体的文本
                var responseText = xmlhttp.responseText;
                //对服务器结果进行处理 
                alert(t);
                //点击加载后 数据输出在页面的div上
                document.getElementById("divContent").innerHTML = t;
                }
            }
        }
    </script>
</body>
ContentServlet.java
@WebServlet("/content")
public class ContentServlet extends HttpServlet{
    public ContentServlet(){
        super();
    }
    //用Ajax进行请求的时候不进行任何页面跳转而是直接输出想产生的数据结果[一般用JSON传递]
    protected void doGet(HttpServletReq req, HttpServletRespon res){
        res.getWritre().println("<b style = 'color:red'>I'm server content</b>");
    }
}

利用Ajax实现新闻列表[案例]

需要下载fast json-1.2.52.jar

News.java
public class News(){
    private String title;
    private String date;
    private String source;
    private String content;
    Getter and Setter + Constructor
}
NewsListServlet.java
@WebServlet("/news_list")
public class NewsListServlet extends HttpServlet{
    public NewsListServlet(){
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List list = new ArrayList();
        list.add(new News("TIOBE:2023年9月份日本排放核污染水","2020-9","TIOBE","..."));
        list.add(new News("TIOBE:2023年9月份日本排放核污染水","2020-9","TIOBE","..."));
        list.add(new News("TIOBE:2023年9月份日本排放核污染水","2020-9","TIOBE","..."));
        list.add(new News("TIOBE:2023年9月份日本排放核污染水","2020-9","TIOBE","..."));
        //用于把List或者java对象給json序列化生成对应的字符串
        String json = JSON.toJSONString(list); //提供的java对象
        System.out.println(json);
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().println(json);
    }
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
</head>
<body>
<div id="container">
</div>
<script type="text/javascript">
        //1.创建XmlHttpRequest对象
        var xmlhttp;
        if(window.XMLHttpRequest){
            xmlhttp = new XMLHttpRequest();
        }else{
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        //2.发送Ajax请求 //F12中Network content有Response返回
        //true代表异步执行 false代表同步执行
        xmlhttp.open("GET", "/json_war_exploded/news_list", false)
        //同步:在网络发布的过程中 数据没有返回的话会一直处于阻塞的状态(前面的没做完后面的不让做)
        xmlhttp.send();
        console.log("请求发送完成");
        if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            //获取响应体的文本
            var text = xmlhttp.responseText;
            //对服务器结果进行处理
            console.log(text);
            var json = JSON.parse(text);//内置的json对象
            console.log(json);
            var html = "";
            for (var i = 0; i < json.length; i++) {
                var news = json[i];
                html = html + "<h1>" + news.title + "</h1>";
                html = html + "<h2>" + news.date + "&nbsp" + news.source + "</h2>";
                html = html + "<hr/>"
            }
            document.getElementById("container").innerHTML = html;
        }
        /*3.处理服务器响应 异步[一直往下进行 onreadystatechange数据监控]
        xmlhttp.onreadystatechange = function(){
            //响应已被接收且服务器处理成功时才执行
            if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
                //获取响应体的文本
                var text = xmlhttp.responseText;
                //对服务器结果进行处理
                console.log(text);
                var json = JSON.parse(text);//内置的json对象
                console.log(json);
                var html = "";
                for (var i = 0; i < json.length; i++) {
                    var news = json[i];
                    html = html + "<h1>" + news.title + "</h1>";
                    html = html + "<h2>" + news.date + "&nbsp" + news.source + "</h2>";
                    html = html + "<hr/>";
                }
                //对html进行动态加载
                document.getElementById("container").innerHTML = html;
            }
        }*/
</script>
</body>
</html>

同步与异步

xmlhttp.open(“GET”, “/json_war_exploded/news_list”, false)

**同步(false)**:程序会阻塞 进入等待的状态 数据不返回时程序是不会往下进行的
异步(true)[推荐]:程序不会阻塞 程序依旧往下进行 数据返回是通过触发onreadystatechange数据监控进行处理的

//    2.发送Ajax请求 //F12中Network content有Response返回
        //true代表异步执行 false代表同步执行
        xmlhttp.open("GET", "/json_war_exploded/news_list", false)
        //同步:在网络发布的过程中 数据没有返回的话会一直处于阻塞的状态(前面的没做完后面的不让做)
        xmlhttp.send();
        console.log("请求发送完成");
        if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            //获取响应体的文本
            var text = xmlhttp.responseText;
            //对服务器结果进行处理
            console.log(text);
            var json = JSON.parse(text);//内置的json对象
            console.log(json);
            var html = "";
            for (var i = 0; i < json.length; i++) {
                var news = json[i];
                html = html + "<h1>" + news.title + "</h1>";
                html = html + "<h2>" + news.date + "&nbsp" + news.source + "</h2>";
                html = html + "<hr/>"
            }
            document.getElementById("container").innerHTML = html;
        }
        /*3.处理服务器响应 异步[一直往下进行 onreadystatechange数据监控]
        xmlhttp.onreadystatechange = function(){
            //响应已被接收且服务器处理成功时才执行
            if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
                //获取响应体的文本
                var text = xmlhttp.responseText;
                //对服务器结果进行处理
                console.log(text);
                var json = JSON.parse(text);//内置的json对象
                console.log(json);
                var html = "";
                for (var i = 0; i < json.length; i++) {
                    var news = json[i];
                    html = html + "<h1>" + news.title + "</h1>";
                    html = html + "<h2>" + news.date + "&nbsp" + news.source + "</h2>";
                    html = html + "<hr/>";
                }
                //对html进行动态加载
                document.getElementById("container").innerHTML = html;
            }
        }*/

<button type = "button" onclick="loadXMLDoc()">修改内容</button>
<script>
/* XMLHttpRequest 用于在后台与服务器交换数据。
这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均内建 XMLHttpRequest 对象。
*/ 
function loadXMLDoc(){
    //AJAX脚本执行
    var xmlhttp;
    if(windows.XMLHttpRequest){
        xmlhttp=new XMLHttpRequest();
    }else{
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
}
</script>

/*
如需将请求发送到服务器,我们使用 XMLHttpRequest 对象的 open() 和 send() 方法:

open(method,url,async)
method:请求的类型;GET 或 POST
url:文件在服务器上的位置
async:true(常用异步)或 false(同步)

对于 web 开发人员来说,发送异步请求是一个巨大的进步。
很多在服务器执行的任务都相当费时。AJAX 出现之前,这可能会引起应用程序挂起或停止。
通过 AJAX,JavaScript 无需等待服务器的响应,而是:
在等待服务器响应时执行其他脚本
当响应就绪后对响应进行处理
*/ 

xmlhttp.open("GET","ajax_info.html",true);
xmlhttp.send(); //send(string) 仅用于POST请求 

/*
若需要来自服务器的响应,请使用XMLHttpRequest对象的responseText或responseXML属性 
responseText => 获得字符串形式的相应数据 如果来自服务器的响应并非XML
responseXML => 获得XML形式的相应数据 如果来自服务器的响应式XML 
*/ 
// responseText 
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
// responseXML
xmlDoc=xmlhttp.responseXML;
txt="";
x=xmlDoc.getElementsByTagName("ARTIST");
    for (i=0;i<x.length;i++)
    {
        txt=txt + x[i].childNodes[0].nodeValue + "<br>";
    }
document.getElementById("myDiv").innerHTML=txt;
/*onreadystatechange事件中我们规定当服务器响应已做好被处理的准备时所执行的任务
0:请求未初始化  1:服务器连接已建立  2:请求已接收  3:请求处理中  4:请求已完成且响应已就绪 
 */ 
 //如果您的网站上存在多个 AJAX 任务,那么您应该为创建 XMLHttpRequest对象编写一个标准的函数
 //并为每个 AJAX 任务调用该函数 
 xmlhttp.onreadystatechange=function()
{
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
    }
}

jQuery对Ajax的支持

  • jQuery对Ajax进行封装,提供了**$.ajax()**方法
  • 语法:**$.ajax(options)**
常用设置项 说明
url 发送请求地址
type 请求类型get|post
data 向服务器传递的参数
dataType 服务器响应的数据类型
text|json|xml|html|jsonp|script
success 接收响应时的处理函数
error 请求失败时的处理函数
jquery_news.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="js/jquery-3.1.1.js"></script>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                "url": "/json_war_exploded/news_list",
                "type": "get",
                "data": "t=tiobe",//多个传参的参数用json格式写
                //"data": {"t":"tiobe", "abc":"123", "uu":"777"}
                //但是实际过程中jquery会以url形式传参 t=tiobe&abc=123&uu=777
                "dataType": "json", //解析成json文件
                "success": function (json){
                    console.log(json);
                    for (var i = 0; i < json.length; i++){
                        $("#container").append("<h1>" + json[i].title + "</h1>"); //append追加
                    }
                },   //ajax的核心信息 ↓
                "error": function (xmlhttp, errorText){
                    console.log(xmlhttp);
                    console.log(errorText);
                    if(xmlhttp.status == "405"){
                        alert("无效的请求方式");
                    }else if(xmlhttp.status == "404"){
                        alert("未找到URL资源");
                    }else if(xmlhttp.status == "500"){
                        alert("服务器内部错误,请联系管理员");
                    }else{
                        alert("产生异常,请联系管理员");
                    }
                }
            })
        })
    </script>
</head>
<body>
    <div id = "container"></div>
</body>
</html>

实现二级联动菜单 [省 市 县]

ChannelServlet.java
package com.example.json;

import com.alibaba.fastjson.JSON;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/channel")
public class ChannelServlet extends HttpServlet {
    public ChannelServlet() {
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String level = req.getParameter("level");
        String parent = req.getParameter("parent");
        List chlist = new ArrayList();
        if(level.equals("1")) {
            chlist.add(new Channel("ai" , "前沿/区块链/人工智能"));
            chlist.add(new Channel("web" , "前端/小程序/JS"));
        }else if(level.equals("2")) {
            if(parent.equals("ai")) {
                chlist.add(new Channel("micro" , "微服务"));
                chlist.add(new Channel("blockchain" , "区块链"));
                chlist.add(new Channel("other" , "..."));
            }else if(parent.equals("web")){
                chlist.add(new Channel("html" , "HTML"));
                chlist.add(new Channel("css" , "CSS"));
                chlist.add(new Channel("other" , "..."));
            }
        }
        //json序列化
        String json = JSON.toJSONString(chlist);
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().println(json);
    }
}
cascade_menu.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="js/jquery-3.1.1.js"></script>
    <script type="text/javascript">
        $(function(){
            $.ajax({
                "url" : "/json_war_exploded/channel",
                "data" : {"level" : "1"},
                "type" : "get" ,
                "dataType" : "json" ,
                "success" : function(json){
                    console.log(json);
                    for(var i = 0 ; i < json.length ; i++){
                        var ch = json[i];
                        //append是在组件内部进行追加内容
                        $("#lv1").append("<option value='" + ch.code + "'>" + ch.name + "</option>")
                    }
                }
            })
        })

        $(function(){ //on是绑定事件 change是当变化时候发生function
            $("#lv1").on("change" , function(){
                var parent = $(this).val();//val()获取输入项的值
                console.log(parent);
                $.ajax({
                    "url" : "/json_war_exploded/channel" ,
                    "data" : {"level" : "2" , "parent" : parent}, //parent 一级频道的value值
                    "dataType" : "json" ,
                    "type" : "get" ,
                    "success" : function(json){
                        console.log(json);
                        //移除所有lv2下的原始option选项
                        $("#lv2>option").remove();
                        for(var i = 0 ; i < json.length ; i++){
                            var ch = json[i];
                            $("#lv2").append("<option value='" + ch.code +"'>" + ch.name + "</option>")
                        }
                    }
                })
            })
        })
    </script>
</head>
<body>
<select id="lv1" style="width:200px;height:30px">
    <option selected="selected">请选择</option>
</select>
<select id="lv2" style="width:200px;height:30px"></select>
</body>
</html>

正则表达式

  • 正则表达式是检查、匹配字符串的表达式
  • 正则表达式是描述规则、主流语言都有良好支持
  • 字符串校验、查找与替换是正则表达式主要使用场景
正则表达式的案例
  • 检查输入的身份证号是否合法(15位、18位)
  • 示例:13010220200328091x
正则表达式:(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)

字符范围匹配

正则表达式 说明 正确 错误
A 精准匹配单个字符 A a
x|y 允许出现的2个字符 y n
[xyz] 字符集合,允许出现集合内任意单个字符 z c
[a-z] [A-Z] [0-9] 字符范围 a D 8 A a A
[^xyz] [‘^0-9] 集合内字符不允许出现 0 A y 8
训练题目
  • 精准匹配字符串”hallo” 或者 “hello” => h**[ae]**llo
  • 请匹配数字范围(0570-0579) => 057[0-9]
  • 单选题只允许输入ABCD其中一项 => [ABCD] / [A-D]

元字符

正则表达式 说明 正确 错误
\d 匹配任意单个数字 8 i
\D 匹配\d规则之外的任意单个字符 i 8
\w 匹配任意单个字母数字下划线 Y &
\W 匹配\w之外的任意单个字符 & Y
\s 匹配单个空格 x
\n 匹配单个换行符 x
. 匹配任意单个字符(换行符除外)
\。 特殊字符,只匹配 “.” . 1
训练题目
  • 请匹配数字(3213.383219)

    \d\d\d\d\.\d\d\d\d\d\d\d
    
  • 匹配杭州与宁波的座机号码(0571|0574-XXXXXXXX)

    057[14]-\d\d\d\d\d\d\d\d
    
  • 请匹配18位身份证号 [第一位1~6和8]

    [1234568]\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d[0-9xX]
    

多次重复匹配

正则表达式 说明 正确 错误
A{3} 精准N次匹配 AAA AA
A{3,} 最少出现N AAA AA
\d{3,5} 约定出现最少次数与最大次数 1234 12
\d* 可以出现零次至无限次,相当于{0,} 1234
\d+ 最少出现一次, 相当于{1,} 12
\d? 最多出现一次, 相当于{0,1} 1 12
联系题目
  • 验证短信验证码(6位数字) => \d[6]

  • 请匹配全国座机号(区号3或4位-电话号码7或8位) => \d[3,4]-\d[7,8]

  • 请匹配英文姓名(例如:James Watson)

    [A-Z][a-z]{1,}\s[A-Z][a-z]{0,}
    [A-Z][a-z]{1,}\s[A-Z][a-z]*
    ^[A-Z][a-z]{1,}+\s[A-Z][a-z]*$
    

定位匹配

正则表达式 说明 正确 错误
^A.* 头匹配 ABC CBA
**.A$* 尾匹配 CBA ABC
^A.*A$ 全字匹配 ACCCA ACCCB
abb123123ab => ^ab.*ab$

贪婪模式[默认匹配规则]

  • 在满足条件的情况下尽可能匹配到字符串
  • 示例:111222333 正则:\d{6,8}
  • 匹配结果:11122233

非贪婪模式

  • 在满足条件的情况下尽可能匹配到字符串

  • 示例:111222333 正则:\d{6,8}?

  • 匹配结果:111222

贪婪模式举例
<a href = "www.baidu.com"> 百度 </a> <a href =  "www.baidu.com">新浪</a>
规则: 想搜索"www.baidu.com" 仅此一个从"开始 从"结束 中间的片段
正则表达式: ".*" 由于默认是贪婪模式 它会找到的结果如下
"www.baidu.com"> 百度 </a> <a href = “www.sina.com”

改成非贪婪模式 正则表达式:".*?"
"www.baidu.com"  "www.baidu.com"

表达式分组

  • 分组将”正则”分组为多个子表达式
  • 示例:abababcdcdcd
  • 正则表达式:(ab){3}(cd){3}
训练题目
  • 匹配验证码(4位或6位)

    (^\d{4}$)|(^\d{6}$)
    
  • 匹配车牌号(冀B-U888G)

    ^([冀黑粤晋][A-Z])-([A-Z0-9]{5})$
    
  • 中文名字匹配 [Unicall码] [张三…]

    ^[\u4e00-\u9fa5]{2,8}$
    
  • 中英文名字匹配

    (^[\u4e00-\u9fa5]{2,8}$)|(^[A-Z][a-z]*$)
    

正则表达式验证JavaScript表单

<body>
    <form action="#" method="post" id="frmInfo">
        <div id="err" style="color : red">
            
        </div> 
        <div>
            姓名: <input id="name" name="name"/>
        </div>
        <div>
            身份证: <input id="idno" name="idno"/>
        </div>
        <div>
            <input type="submit"/>
        </div>
    </form>
    <script type="text/javascript">
        document.getElementById("frmInfo").onsubmit = function(){
            //在JS中定义正则表达式对象只需要在 /正则表达式/
            var regex1 = /^[\u4e00-\u9fa5]{2,8}$/
            var regex2 = /^rehextal
            var name = document.getElementById("name").value;
            var idno = document.getElementByid("inamd")
            if(regex1.test(name)==false); //返回true或false校验是否成功
                document.getElementById("err").innerHTM="无效姓名";
                return false;
            }else if(regx2.test(idno) == false){
                document.getElementById("err").innerHTM="无效身份证号";
            }else{
                alert("验证通过准备提交")
                return true;
            }
    </script>
</body>

Java中web页面信息提取

sample.html
<!DOCTYPEhtml>
<html>
<head>
<meta charset="UTF-8">
<title>国际主要城市</title>
</head>
<body>
    <h1>国际主要城市</h1>
    <ul>
        <li>纽约NewYork</li>
        <li>伦敦London</li>
        <li>东京Tokyo</li>
        <li>巴黎Paris</li>
        <li>香港HongKong</li>
        <li>新加坡Singapore</li>
        <li>悉尼Sydney</li>
        <li>米兰Milano</li>
        <li>上海Shanghai</li>
        <li>北京Beijing</li>
        <li>马德里Madrid</li>
        <li>莫斯科Moscow</li>
        <li>首尔Seoul</li>
        <li>曼谷Bangkok</li>
        <li>多伦多Toronto</li>
        <li>布鲁塞尔Brussels</li>
        <li>芝加哥Chicago</li>
        <li>吉隆坡KualaLumpur</li>
        <li>孟买Mumbai</li>
        <li>华沙Warsaw</li>
        <li>圣保罗SaoPaulo</li>
        <li>苏黎世Zurich</li>
        <li>阿姆斯特丹Amsterdam</li>
        <li>墨西哥城MexicoCity</li>
        <li>雅加达Jakarta</li>
        <li>都柏林Dublin</li>
        <li>曼谷Bangkok</li>
        <li>台北Taipei</li>
        <li>伊斯坦布尔Istanbul</li>
        <li>里斯本Lisbon</li>
        <li>罗马Rome</li>
        <li>法兰克福Frankfurt</li>
        <li>斯德哥尔摩Stockholm</li>
        <li>布拉格Prague</li>
        <li>维也纳Vienna</li>
        <li>布达佩斯Budapest</li>
        <li>雅典Athens</li>
        <li>加拉加斯Caracas</li>
        <li>洛杉矶LosAngeles</li>
        <li>新西兰NewZealand</li>
        <li>圣地亚哥SanDiego</li>
        <li>布宜诺斯艾利斯BuenosAires</li>
        <li>华盛顿Washington</li>
        <li>墨尔本Melbourne</li>
        <li>约翰内斯堡Johannesburg</li>
        <li>亚特兰大Atlanta</li>
        <li>巴塞罗那Barcelona</li>
        <li>旧金山SanFrancisco</li>
        <li>马尼拉Manila</li>
        <li>波哥大Bogota</li>
        <li>特拉维夫TelAviv-Yafo</li>
        <li>新德里NewDelhi</li>
        <li>迪拜Dubai</li>
        <li>布加勒斯特Bucharest</li>
        <li>奥斯陆Oslo</li>
        <li>柏林Berlin</li>
        <li>赫尔辛基Helsinki</li>
        <li>日内瓦Geneva</li>
        <li>利雅得Riyadh</li>
        <li>哥本哈根Copenhagen</li>
        <li>汉堡Hamburg</li>
        <li>开罗Cairo</li>
        <li>卢森堡Luxembourg</li>
        <li>班加罗尔Bangalore</li>
        <li>达拉斯Dallas</li>
        <li>科威特城Kuwaitcity</li>
        <li>波士顿Boston</li>
        <li>慕尼黑Munich</li>
        <li>迈阿密Miami</li>
        <li>利马Lima</li>
        <li>基辅Kiev</li>
        <li>休斯顿Houston</li>
        <li>广州Guangzhou</li>
        <li>贝鲁特Beirut</li>
        <li>卡拉奇Karachi</li>
        <li>索菲亚Sophia</li>
        <li>蒙得维的亚Montevideo</li>
        <li>里约热内卢RioDEJaneiro</li>
        <li>胡志明市HoChiMinhCity</li>
        <li>蒙特利尔Montreal</li>
        <li>内罗毕Nairobi</li>
        <li>巴拿马城Panamacity</li>
        <li>金奈Chennai</li>
        <li>布里斯班Brisbane</li>
        <li>卡萨布兰卡Casablanca</li>
        <li>丹佛Denver</li>
        <li>基多Quito</li>
        <li>斯图加特Stuttgart</li>
        <li>温哥华Vancouver</li>
        <li>麦纳麦MaiNaMai</li>
        <li>危地马拉市Guatemalacity</li>
        <li>开普敦CapeTown</li>
        <li>圣何塞SanJose</li>
        <li>西雅图Seattle</li>
        <li>深圳Shenzhen</li>
        <li>珀斯Perth</li>
        <li>加尔各答Calcutta</li>
        <li>安特卫普Antwerp</li>
        <li>费城Philadelphia</li>
        <li>鹿特丹Rotterdam</li>
        <li>拉各斯Lagos</li>
        <li>波特兰Portland</li>
        <li>底特律Detroit</li>
        <li>曼彻斯特Manchester</li>
        <li>惠灵顿Wellington</li>
        <li>里加Riga</li>
        <li>爱丁堡Edinburgh</li>
        <li>圣彼得堡StPetersburg</li>
        <li>圣迭戈SanDiego</li>
        <li>伊斯兰堡Islamabad</li>
        <li>伯明翰Birmingham</li>
        <li>多哈Doha</li>
        <li>阿拉木图AlmaAtaAlmaty</li>
        <li>卡尔加里Calgary</li>
    </ul>
</body>
</html>
RegexSample.java
package com.imooc.regex;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexSample {
    public static void main(String[] args) {
        StringBuilder content = new StringBuilder();
        try {//读取文件
            FileInputStream fis = new FileInputStream("D:/workspace/regex/WebContent/sample.html");
            InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
            BufferedReader bufferedReader = new BufferedReader(isr);//缓冲提高读取效率
            String lineText = "";//每一行
            while((lineText = bufferedReader.readLine()) != null) {//读取完成
//                System.out.println(lineText);
                content.append(lineText + "\n");//追加到后面完整字符串
            }
            bufferedReader.close();
            System.out.println(content);
        
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        //1.创建正则表达式对象 用group进行分组提取
        //[正则表达式字符串] \\两个斜杠是原意输出 +至少出现一次
        Pattern p = Pattern.compile("<li>([\\u4e00-\\u9fa5]{2,10})([a-zA-Z]+)</li>");
        //2.匹配正则表达式
        Matcher m = p.matcher(content);
        //3.查找匹配的结果
        while(m.find()) {//原始字符串中进行查找 有返回true 无返回false
//            System.out.println(m.group(0)); //完整字符串
            String chs = m.group(1); //group分组
            String eng = m.group(2);
            System.out.println(chs + "-" + eng);
        }
    }
}

初始过滤器-Filter [机场检查]

  • 过滤器(Filter)是J2EE Servlet模块下的组件
  • Filter的作用是对URL进行统一的拦截处理
  • Filter通常用于应用程序层面进行全局处理

开发过滤器三要素

  • 任何过滤器都要实现 javax.servlet.Filter 接口
  • 在Filter接口的doFIlter()方法中编写过滤器的功能代码
  • 在web.xml中对过滤器进行配置,说明拦截URL的范围
MyFirstFilter.java
public class MyFirstFilter implements Filter{
   public void destroy(){}
   public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       System.out.println("过滤器已生效");
       chain.doFilter(req, resp);
   }
   public void init(FilterConfig filterConfig)thorws ServletException{}
}
<filter>
    <filter-name>MyFirstFilter</filter-name>
    <filter-class>filter.MyFirstFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFirstFilter</filter-name>
    <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>

<!-- 
    filter-mapping标签用于说明过滤器对URL应用的范围
    1. filter-name 过滤器名称与filter.filter-name保持一致
    2. url-pattern 说明过滤器作用范围 "/*"代表对所有url进行过滤
-->
<body>
    我是默认首页
</body>
HelloServlet.java
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
    public HelloServlet(){}
    protected void doGet(HttpServletReq req, HttpServletResp resp){
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().println("Hello World!!!")
    }
}

过滤器的生命周期

  • 初始化init() - Filter.init()
  • 提供服务 - Filter.doFilter()
  • 销毁 - Filter.destroy()

过滤器特性

  • 过滤器对象在Web应用启动时被创建且全局唯一
  • 唯一的过滤器对象在并发环境中采用”多线程“提供服务

过滤器两种开发方式

过滤器的配置形式
<filter>
   <filter-name>MyFirstFilter</filter-name>
   <filter-class>filter.MyFirstFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>MyFirstFilter</filter-name>
   <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>
过滤器的注解形式
@WebFilter(filterName = "MyAnnoationFilter", urlPatterns = "/*")
public class MyAnnoationFilter implements Filter{
    
}

MyAnnoationFilter.java      //自定义过滤器名称  设置去过滤哪些url
@@WebFilter(filterName = "MyAnnoationFilter", urlPatterns = "/*")
public class MyAnnoationFilter implements Filter{
    public void destroy(){}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
        System.out.println("过滤器已生效");
        chain.doFilter(req, resp);
    }
    public void init(FilterConfig filterConfig)thorws ServletException{}
}

配置与注解如何选择

  • 配置形式维护性更好,适合应用全局过滤 [中, 大型项目]
  • 注解形式开发体验更好,适合于小型项目敏捷开发

Web中文乱码的解决 [需要强制转换]

  • GET请求-server.xml增加URIEncoding = “UTF-8”;
  • POST请求-使用request.setCharacterEncoding(“UTF-8”);
  • 响应-response.setContentType(“text/html; charset = UTF-8”);
CharacterEncodingFilter.java
public class CharacterEncodingFilter implements Filter{
    public void destroy(){}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       //上面的不是Httpservlet要自己写HttpServlet并导入 解决post请求中的中文乱码
        HttpServletRequest req = (HttpServletRequest)request;//解决中文乱码问题
        req.serCharacterEncoding("UTF-8");
        HttpServletResponse res = (HttpServletResponse)response;//解决中文乱码问题
        res.serContentType("text/html;charset=UTF-8");
        chain.doFilter(req, response);
    }
    public void init(FilterConfig filterConfig)thorws ServletException{}
}
ServletRequest[最顶级] 和 HTTPServletRequest[需继承最顶级]关系所在

web.xml
<filter>
    <filter-name>MyFirstFilter</filter-name>
    <filter-class>filter.MyFirstFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFirstFilter</filter-name>
    <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>

====================================================================

<!--或者在CharacterEncodingFilter上方进行注解 必须是全局唯一-->
@WebFilter(filterName="CharacterEncodingFilter", urlPatterns="/*")
HelloServlet.java
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
    public HelloServlet(){}
    protected void doGet(HttpServletReq req, HttpServletResp resp){
        resp.getWriter().println("你好!世界!!")
    }
}

过滤器开发技巧

过滤器参数化
  • 过滤器为了增强灵活性,允许配置信息放在web.xml
  • 在web.xml中配置**< init-param >**设置过滤器参数
优化字符集过滤器 [为了不去修改java代码]
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value> <!--方便参数发生变化-->
    </init-param>
    <init-param>
        <param-name>v1</param-name>
        <param-value>GBK</param-value>
    </init-param>
    ......
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>
-------------------------------------------------------------
<!--或者在CharacterEncodingFilter上方进行注解 必须是全局唯一-->
@WebFilter(filterName="CharacterEncodingFilter", urlPatterns="/*",initParams={
    @WebInitParam(name="encoding", value="UTF-8"),
    @WebInitParam(name="p1", value="v1")
})
-------------------------注意init与doFilter----------------------------
public class CharacterEncodingFilter implements Filter{
    private String encoding; //类中的全局私有变量
    public void init(FilterConfig filterConfig)thorws ServletException{
        encoding=filterConfig.getInitParameter("encoding");
        System.out.println(encoding); //用debug打开
    }
    public void doFilter(ServletRequest req, ServeltResponse resp, FilterChain chain)throws IOException,ServletException{
       //上面的不是Httpservlet要自己写HttpServlet并导入 解决post请求中的中文乱码
        HttpServletRequest req = (HttpServletRequest)request;//解决中文乱码问题
        req.setCharacterEncoding("encoding");
        HttpServletResponse res = (HttpServletResponse)response;//解决中文乱码问题
        res.setContentType("text/html;charset=" + encoding);// GBK/UTF-8或者其他
        chain.doFilter(req, response);
    }
}

url-pattern设置过滤范围

url-pattern常用写法
  • /index.jsp - 执行资源精准匹配
  • /servlet/* - 以前缀进行模糊匹配
  • *.jsp - 以后缀进行模糊匹配
SampleServlet1.java
@WebServlet("/servlet/sample1")
public class SampleServlet1 extends HttpServlet{
   public SampleServlet1(){}
   protected void doGet(HttpServletRequest req,HttpServletResponse resp){
       resp.getWriter().println("I'm" + this.getClass().getSimpleName());
   }
}
UrlPatternFilter.java
public class UrlPatternFilter implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
//上面的不是Httpservlet要自己写HttpServlet并导入 解决post请求中的中文乱码
    HttpServletRequest req = (HttpServletRequest)request;//解决中文乱码问题
    HttpServletResponse res = (HttpServletResponse)response;//解决中文乱码问题
}
<filter>
    <filter-name>UrlPatternFilter</filter-name>
    <filter-class>filter.UrlPatternFilter</filter-class>
</filter>
    <filter-mapping> <!--  <url-pattern>/test.jsp</url-pattern> <!--只对其进行过滤 -->
        <param-name>UrlPatternFilter</param-name>
        <url-pattern>/servlet/*</url-pattern>
    </filter-mapping>
    ......

在控制台Console显示:拦截到http://localhost:8080/url-pattern/test.jsp 因为这是精准匹配 需要换成 /*

/ 映射的问题

  • / 指映射Web应用根路径, 且只会对Servlet生效
  • 默认首页index.jsp会让 / 失效
  • //* 含义不同, 前者指向根路径, 后者代表所有
SampleServlet2.java
@WebServlet("/")
public class SampleServlet2 extends HttpServlet{
    public SampleServlet1(){}
    protected void doGet(HttpServletRequest req,HttpServletResponse resp){
        resp.getWriter().println("I'm" + this.getClass().getSimpleName());
    }
}
<filter>
    <filter-name>UrlPatternFilter</filter-name>
    <filter-class>filter.UrlPatternFilter</filter-class>
</filter>
    <filter-mapping>
        <param-name>UrlPatternFilter</param-name>
        <url-pattern>/</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <param-name>UrlPatternFilter</param-name>
        <url-pattern>/servlet/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <param-name>UrlPatternFilter</param-name>
        <url-pattern>*.jsp</url-pattern>
    </filter-mapping>
    ......
-------------------------------------------------------------
<!--或者在UrlPatternFilter上方进行注解 必须是全局唯一-->
@WebFilter(filterName="UrlPatternFilter", urlPatterns={
    "/","/servlet/*","*.jsp"
})

这个url-pattern 中的**/** 只会映射到根路径的SampleServlet2

@WebServlet(“/“)
public class SampleServlet2 extends HttpServlet{

Web.xml中默认配置了首页为index.jsp 如果想对默认首页拦截需要写
< url-pattern> ***/** < /url-pattern> 或 < url-pattern> /index.jsp < /url-pattern>
默认首页优先级比servlet要高

过滤链

过滤链开发注意事项
  • 每一个过滤器应具有单独职能
  • 过滤器的执行顺序以**< filter-mapping >**的前后顺序为准
  • 调用**chain.doFilter()**将请求向后传递
FilterA.java
public class FilterA implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       System.out.println("I'm Filter A");
       chain.doFilter(req, resp);
    }
    public void destroy(){}

FilterB.java
public class FilterA implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       System.out.println("I'm Filter B");
       chain.doFilter(req, resp);
    }
    public void destroy(){}

FilterC.java
public class FilterA implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       System.out.println("I'm Filter C");
       chain.doFilter(req, resp);
    }
    public void destroy(){}
<filter>
    <filter-name>FilterA</filter-name>
    <filter-class>filter.FilterA</filter-class>
</filter>
<filter>
    <filter-name>FilterB</filter-name>
    <filter-class>filter.FilterA</filter-class>
</filter>
<filter>
    <filter-name>FilterC</filter-name>
    <filter-class>filter.FilterA</filter-class>
</filter>
    <filter-mapping>
        <param-name>FilterA</param-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <param-name>FilterB</param-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <param-name>FilterC</param-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
---------------------不推荐使用注解--------------------------
用注解方式需要在每个Filter.java上面添加注解 
@WebFilter(filterName="FilterA", urlPatterns="/*")
public class FilterA implements Filter{
    //按照字母表升序排序 且不区分大小写
}
HelloServlet.java
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
    public HelloServlet(){}
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException{
        response.getWriter().print("Hello World!");
        Sysotem.out.println("Hello World!")
    }
}

Console显示:顺序由决定
I’m Filter A //chain.doFilter(req, resp); 请求顺着过滤链往下走
I’m Filter B
I’m Filter C //过滤链没有格外的过滤器了 回到HelloServlet.java最终处理
Hello World!
//servlet处理完以后按照原先的顺序 逆向由Hello、C、B、A顺序返回

刻意去控制过滤器访问 VPN 仅限中国访问 如果是中国地址就调用chain.doFilter进行放行 其本质是防火墙

多端设备自动匹配(Pc端 移动端)

设备适配过滤器
index.html
自动脑补绘制两个html首页
一个是电脑端的图片展示
一个是手机端的图片展示
DeviceAdapterFilter.java
public class DeviceAdapterFilter implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       HttpServletRequest req = (HttpServletRequest)request;//强制类型转换
          HttpServletResponse res = (HttpServletResponse)response;
       /*
       /index.html
        PC: /desktop/index.html
        MOBILE: /mobile/index.html

       /test.html
        PC: /desktop/test.html
        MOBILE: /mobile/test.html
       */
       String uri = req.getRequestURI();
       System.out.println("URI: " + uri);
       if(uri.startsWith("/desktop") || uri.startsWith("/mobile")){
           chain.doFilter(req, resp);
       }else {    //读取客户端请求头 所有字符串转成小写
           String userAgent = req.getHeader("user-agent").toLowerCase(); 
           String targetURL = "";
           if(userAgent.indexOf("android")!=-1 || userAgent.indexOf("iphone")!=-1){
               targetURI = "/mobile" + uri;
               System.out.println("移动端设备正在访问, 重新跳转URI: " + targetURI)
               res.sendRedircet(targetURI); //向/mobile对应页面进行获取
           }else {
               targetURI = "/desktop" + uri;
                System.out.println("PC端设备正在访问, 重新跳转URI: " + targetURI)
               res.sendRedircet(targetURI); //向/mobile对应页面进行获取
           }
       }
    }
    public void destroy(){}
}
<filter>
    <filter-name>DeviceAdapterFilter</filter-name>
    <filter-class>filter.DeviceAdapterFilter</filter-class>
</filter>
    <filter-mapping>
        <param-name>DeviceAdapterFilter</param-name>
        <url-pattern>*.html</url-pattern> <!--对其进行过滤-->
    </filter-mapping>

URI: /index.html
PC端设备正在访问, 重新跳转URI: /desktop/index.html
URI: /desktop/index.html

++++++++++++++++++++++++++++++++++++++++++++

URI: /index.html
移动端设备正在访问, 重新跳转URI: /mobile/index.html
URI: /mobile/index.html

监听器、Freemarker

监听器:对Web应用对象的行为进行监控 [触发事件后进行捕获] ★★★
Freemarker[模板引擎]:模板脚本+数据来实现最终数据的产生 ★★★★★

监听器

生活中的”监听器”:汽车自动刹车系统 自动检测前方障碍物触发自动刹车;实时监控电表水表 钱不足直接断电

监听器 - Listener
  • 监听器(Listener)是J2EE Servlet模块下的组件
  • Listener的作用对Web应用对象的行为进行监控
  • 通过Listener监听自动触发指定的功能代码

三种监听对象

  • ServletContext - 对全局ServletContext及其属性进行监听
  • HttpSession - 对用户会话及其属性操作进行监听
  • ServletRequest - 对请求及属性操作进行监听
过滤器与监听器的区别
  • 过滤器(Filter)的职责是对**URL进行过滤拦截**, 是主动的执行
  • 监听器(Listener)的职责是对**Web对象进行监听**, 是被动触发

开发监听器三要素

  • 实现XxxListener接口, 不同接口对应不同监听对象
  • 实现每个接口中独有的方法, 实现触发监听的后续操作
  • 在web.xml中配置**< listener >**使监听器生效

``

第一个监听器 [全局推荐使用配置形式]

FirstListener.java //Debug启动
@WebListener //启动时tomcat会自动扫描
public class FirstListener implements ServletContextListener{
    //项目初始化所触发
    public void contextInitialized(ServletContextEvent sce){
        System.out.println("ServletContext已初始化");
    }
    //上下文被销毁时所触发 关闭时自动销毁
    public void contextDestoryed(ServletContextEvent sce){
       System.out.println("ServletContext已销毁");
    }
}
<listener>
    <listener-class>com.imooc.listener.FirstListener</listener-class>
</listener>

内置对象监听接口

  • ServletContextListener - 监听ServletContext对象创建、销毁等操作
  • HttpSessionListener - 监听HttpSession对象创建、销毁等操作
  • ServletRequestListener - 监听HttpServletRequest对象创建、销毁等操作
<listener>
    <listener-class>com.example.json.WebListener</listener-class>
</listener>
HelloServlet.java //设置属性的时候会触发WebListener
WebServlet("/hello")
public class HelloServlet extends HttpServlet{
    public HelloServlet(){}
    protected void doGet(HttpServletRequest req,HttpServletResponse resp){
        resp.getWriter().println("Hello World");
        req.getServletContext().setAttribute("sc-attr1","sc-attr-value1");
        req.getSession().setAttribute("session-attr1","session-attr-value1");
        req.setAttribute("request-attr1","request-attr-value1");
    }
}
WebListener.java
package com.example.json;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class WebListener implements ServletContextListener, HttpSessionListener, ServletRequestListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext已初始化");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext已被销毁");
    }

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        System.out.println("Session已被创建, SessionId:" + session);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        System.out.println("Session已被销毁");
    }

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        System.out.println("HttpServletRequest已被销毁");
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        HttpServletRequest request = (HttpServletRequest)servletRequestEvent.getServletRequest();
        //请求初始化完毕
        System.out.println("HttpServletRequest已被创建, URI" + request.getRequestURI());
    }
}

//session被创建了以后 第二次从同样的浏览器发出的请求覆盖了sessionid(3min后过期可人为而改变) 隐藏且可用
HttpServletRequest已被创建, URI/json_war_exploded/
Session已被创建, SessionId:org.apache.catalina.session.StandardSessionFacade@71ed02bc
HttpServletRequest已被销毁
HttpServletRequest已被创建, URI/json_war_exploded/
Session已被创建, SessionId:org.apache.catalina.session.StandardSessionFacade@194a08e0
HttpServletRequest已被销毁

属性监听接口 [了解]

  • ServletContextAttributeListener - 监听全局属性操作
  • HttpSessionAttributeListener - 监听用户会话属性操作
  • ServletRequestAttributeListener - 监听请求属性操作
web.xml 和 helloServlet.java的代码都一样
WebAttributeListener.java
package com.example.json;

import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

public class WebAttributeListener implements ServletContextAttributeListener, HttpSessionAttributeListener, ServletRequestAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
        System.out.println("ServletContext新增属性:" + servletContextAttributeEvent.getName() + "->" + servletContextAttributeEvent.getValue());
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {

    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {

    }

    @Override
    public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) {
        System.out.println("ServletRequestAttributeEvent新增属性:" + servletRequestAttributeEvent.getName() + "->" + servletRequestAttributeEvent.getValue());

    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent) {

    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent) {

    }

    @Override
    public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
        System.out.println("HttpSessionBindingEvent新增属性:" + httpSessionBindingEvent.getName() + "->" + httpSessionBindingEvent.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {

    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {

    }
}

ServletContext已初始化
[2023-09-28 04:44:52,590] Artifact json:war exploded: Artifact is deployed successfully
[2023-09-28 04:44:52,590] Artifact json:war exploded: Deploy took 394 milliseconds
HttpServletRequest已被创建, URI/json_war_exploded/
ServletContext新增属性:org.apache.jasper.runtime.JspApplicationContextImpl->org.apache.jasper.runtime.JspApplicationContextImpl@7753fcd4
ServletContext新增属性:org.apache.jasper.compiler.ELInterpreter->org.apache.jasper.compiler.ELInterpreterFactory$DefaultELInterpreter@3b424c58
ServletContext新增属性:org.apache.jasper.compiler.StringInterpreter->org.apache.jasper.compiler.StringInterpreterFactory$DefaultStringInterpreter@15bdbfaf
Session已被创建, SessionId:org.apache.catalina.session.StandardSessionFacade@6baa8579
HttpServletRequest已被销毁
HttpServletRequest已被创建, URI/json_war_exploded/
Session已被创建, SessionId:org.apache.catalina.session.StandardSessionFacade@2843a202
HttpServletRequest已被销毁
HttpServletRequest已被创建, URI/json_war_exploded/hello
ServletContext新增属性:sc-attr1->sc-attr-value1
HttpSessionBindingEvent新增属性:session-attr1->session-attr-value1
ServletRequestAttributeEvent新增属性:request-attr1->request-attr-value1
HttpServletRequest已被销毁

监听器的应用场景

请求流量分析 [以图表形式展现]

流程:在应用启动的时候将两个List初始化 一个保存时间一个保存数值 分情况 若时间不存在初始化1 若存在则在原始+1进行更新操作

web.xml
<listener>
  <listener-class>com.example.json.RequestTotalListener</listener-class>
</listener>
RequestTotalListener.java
package com.example.json;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class RequestTotalListener implements ServletContextListener, ServletRequestListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        List timeList = new ArrayList(); //时间数据
        List valueList = new ArrayList(); //具体时间访问量数据
        servletContextEvent.getServletContext().setAttribute("timeList", timeList);//得到最原始的context
        servletContextEvent.getServletContext().setAttribute("valueList", valueList);//启动ServletContext时自动启动倆请求
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {

    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        //请求初始化 每来一个新的请求监听器都会执行此方法 记录某一时间点的访问量
        //TimeList:10:02  10:03  10:04  10:05
        //ValueList: 5       7     10    1+1=2
        List<String> timeList = (List)servletRequestEvent.getServletContext().getAttribute("timeList");
        List<Integer> valueList = (List)servletRequestEvent.getServletContext().getAttribute("valueList");
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); //按照小时分钟来取
        String time = sdf.format(date); //date对象传入
        if (timeList.indexOf(time) == -1) { //查找对应的数据在集合中是否存在
            //若时间不存在 则追加时间
            timeList.add(time);
            valueList.add(1);//当前有1个请求被创建
            servletRequestEvent.getServletContext().setAttribute("timeList", timeList);
            servletRequestEvent.getServletContext().setAttribute("valueList", valueList);
        }else { //假设时间存在 不增加新数据 在原有数据加1
            int index = timeList.indexOf(time); //10:05返回索引值3
            int value = valueList.get(index);
            valueList.set(index, value+1);
            servletRequestEvent.getServletContext().setAttribute("valueList", valueList);
        }
    }
}
RequestTotalServlet.java
package com.imooc.total;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;

/**
 * Servlet implementation class RequestTotalServlet
 */
@WebServlet("/rt")
public class RequestTotalServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public RequestTotalServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext context = request.getServletContext();//得到全局context对象
        List<String> timeList = (List)context.getAttribute("timeList");//提取之前创建的
        List<Integer> valueList = (List)context.getAttribute("valueList");
        response.setContentType("text/html;charset=utf-8");
        /*response.getWriter().println(timeList.toString()); //打印输出时间数值数据
        response.getWriter().println("<br/>");
        response.getWriter().println(valueList.toString());*/
        
        Map result = new HashMap();
        result.put("timeList", timeList);
        result.put("valueList", valueList);
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }
}
text3.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test Page</title>
</head>
<body>
<h1>I'm test page 3</h1>
</body>
</html>

<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/echarts.min.js"></script>
<script type="text/javascript" src="js/jquery-3.1.1.js"></script>
</head>
<body>
   <div id="main" style="width: 600px; height: 400px;"></div>
   <script type = "text/javascript">
     //基于准备好的dom, 初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));
     //指定图表的配置项和数据
    var option = {
        title:{
            text:'Echarts 入门示例'
        },
        tooltip: {},
        legend: {
            data: ['销量']
        },
        xAxis: {
            data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
        },
        yAxis: {},
        series:[{
            name: '销量',
            type: 'bar',
            data: [5,20,36,10,10,20]
        }]
    };
       //使用刚指定的配置项和数据显示图表
       myChart.setOption(option);
    </script>
</body>

入门百度Echarts组件

利用Ajax和Jquery进行前后端通信

Apache ECharts

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    <script type="text/javascript" src="js/echarts.min.js"></script>
    <script type="text/javascript" src="js/jquery-3.1.1.js"></script>
</head>
<body>
<div id="main" style="width: 600px; height: 400px;"></div>
<script type="text/javascript">
    //每一秒钟向服务器查询一次数据把变化数据重新写入图表
//需要Javascript定时器完成
    function showChat(){//显示图表
        //基础的通讯部分
    $.ajax({
        url:"./rt",
        type:"get",
        dataType:"json",
        success:function(json){
            console.log(json.timeList);
            console.log(json.valueList);
    //基于准备好的dom, 初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));
    //指定图表的配置项和数据
    var option = {
        title: {
            text: '请求流量分析统计'
        },
        tooltip: {},
        legend: {
            data: ['访问量']
        },
        xAxis: {
           // data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
            data: json.timeList
        },
        yAxis: {},
        series: [{
            name: '访问量',
            type: 'line', //柱状图  line是折线图
           // data: [5, 20, 36, 10, 10, 20] //图中展示的数值 与商法data对应
            data: json.valueList
        }]
    };
    //激活使用刚指定的配置项和数据显示图表
    myChart.setOption(option);
        }
    })
}
       window.setInterval("shwoChart()", 1000);//间隔某时长去执行指定代码
    //每秒钟通过shwoChart()向服务器"./rt"发送请求 对此请求进行排除用RequestTotalListener去改写
</script>
</body>
</html>
RequestTotalListener.java
package com.example.json;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class RequestTotalListener implements ServletContextListener, ServletRequestListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        List timeList = new ArrayList(); //时间数据
        List valueList = new ArrayList(); //具体时间访问量数据
        servletContextEvent.getServletContext().setAttribute("timeList", timeList);//得到最原始的context
        servletContextEvent.getServletContext().setAttribute("valueList", valueList);//启动ServletContext时自动启动倆请求
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {

    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        HttpServletRequest request = (HttpServletRequest)servletRequestEvent.getServletRequest();
        String url = request.getRequestURL().toString();
        if (url.endsWith("/rt") == true){
            return; //将rt的url排除之外
        }
        //请求初始化 每来一个新的请求监听器都会执行此方法 记录某一时间点的访问量
        //TimeList:10:02  10:03  10:04  10:05
        //ValueList: 5       7     10    1+1=2
        List<String> timeList = (List)servletRequestEvent.getServletContext().getAttribute("timeList");
        List<Integer> valueList = (List)servletRequestEvent.getServletContext().getAttribute("valueList");
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); //按照小时分钟来取
        String time = sdf.format(date); //date对象传入
        if (timeList.indexOf(time) == -1) { //查找对应的数据在集合中是否存在
            //若时间不存在 则追加时间
            timeList.add(time);
            valueList.add(1);//当前有1个请求被创建
            servletRequestEvent.getServletContext().setAttribute("timeList", timeList);
            servletRequestEvent.getServletContext().setAttribute("valueList", valueList);
        }else { //假设时间存在 不增加新数据 在原有数据加1
            int index = timeList.indexOf(time); //10:05返回索引值3
            int value = valueList.get(index);
            valueList.set(index, value+1);
            servletRequestEvent.getServletContext().setAttribute("valueList", valueList);
        }
    }
}
RequestTotalServlet.java
package com.example.json;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;

/**
 * Servlet implementation class RequestTotalServlet
 */
@WebServlet("/rt")
public class RequestTotalServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public RequestTotalServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext context = request.getServletContext();
        List<String> timeList = (List)context.getAttribute("timeList");
        List<Integer> valueList = (List)context.getAttribute("valueList");
        response.setContentType("text/html;charset=utf-8");
        /*response.getWriter().println(timeList.toString());
        response.getWriter().println("<br/>");
        response.getWriter().println(valueList.toString());*/ //将对象进行封装
        
        Map result = new HashMap();
        result.put("timeList", timeList);
        result.put("valueList", valueList);
        String json = JSON.toJSONString(result); //将java对象转换为json字符串
        response.getWriter().println(json);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }
}

静态数据预加载

通过监听器在上下文初始化的时候放在系统的全局属性中

Channel.java
public class Channel{
    private String channelName;
    private String url;
    ......
}
StaticDataListener.java
package com.imooc.listener;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import com.imooc.listener.entity.Channel;

public class StaticDataListener implements ServletContextListener{

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // TODO Auto-generated method stub
        List list  = new ArrayList();
        list.add(new Channel("免费课程" , "http://www.imooc.com/1"));
        list.add(new Channel("实战课程" , "http://www.imooc.com/2"));
        list.add(new Channel("就业班" , "http://www.imooc.com/3"));
        sce.getServletContext().setAttribute("channelList", list);
        //一次性写入到全局属性种
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // TODO Auto-generated method stub
        
    }

}
web.xml
<listener>
    <listener-class>listener.StaticDataListener</listener-class>
</listener>
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:forEach items="${applicationScope.channelList }" var="c"> <!--有效数据提取-->
    <a href="${c.url }">${c.channelName }</a> | 
</c:forEach>
<hr/>
</body>
</html>

FreeMarker(FTL)模板引擎[可以替代jsp]

FreeMarker是一款用java语言编写的模版引擎,它虽然不是web应用框架,但它很合适作为web应用框架的一个组件。

特点:
1.轻量级模版引擎,不需要Servlet环境就可以很轻松的嵌入到应用程序中
2.能生成各种文本,如html,xml,java
3.入门简单,它是用java编写的,很多语法和java相似

基础语法及入门基础

freemarker模板文件(*.ftl)的基本组成部分
1.文本:直接输出的内容部分
2.注释:不会输出的内容,格式为 <#– 注释内容 –>
3.取值(插值):代替输出数据模型的部分,格式为${数据模型}或#{数据模型}
4.ftl指令:Freemarker指令,类似于HTML标记。

字符输出
$(emp.name?if_exists)  //变量存在,输出该变量,否则不输出
${emp.name!}       //变量存在,输出该变量,否则不输出
${emp.name?default("xxx")} // 变量不存在,取默认值xxx
${emp.name!"xxx"}           // 变量不存在,取默认值xxx
常用内部函数
${"123<br>456"?html}   // 对字符串进行HTML编码,对html中特殊字符进行转义
${"str"?cap_first}    // 使字符串第一个字母大写 
${"Str"?lower_case}    // 将字符串转换成小写
${"Str"?upper_case}   // 将字符串转换成大写
${"str"?trim}            // 去掉字符串前后的空白字符
字符串的两种拼接方法
${"hello${emp.name!}"} //输出hello+变量名
${"hello"+emp.name!}   //使用+号来连接,输出hello+变量名
截取子串
可以通过如下语法来截取子串:
<#assign str = "abcdefghijklmn"/>
// 方法1${str?substring(0,4)} // 输出abcd
// 方法2${str[0]}${str[4]} // 结果是ae
${str[1..4]}     // 结果是bcde// 返回指定字符的索引${str?index_of("n")}
日期输出
${emp.date?string('yyyy-MM-dd')} //日期格式
数字输出(以数字20为例)
${emp.name?string.number} //输出20
${emp.name?string.currency} //¥20.00
${emp.name?string.precent} //20%
${1.222?int}          // 将小数转为int,输出1
<#setting number_format="percent"/> // 设置数字默认输出方式('percent',百分比)
<#assign answer=42/>          // 声明变量 answer 42
#{answer}          // 输出 4,200%
${answer?string}      // 输出 4,200%
${answer?string.number}   // 输出 42
${answer?string.currency} // 输出 ¥42.00
${answer?string.percent}  // 输出 4,200%
#{answer}         // 输出 42
比较运算符
表达式中支持的比较运算符有如下几个:= 或 == :判断两个值是否相等.
!= :判断两个值是否不等.
> 或 gt :判断左边值是否大于右边值
>= 或 gte :判断左边值是否大于等于右边值
< 或 lt :判断左边值是否小于右边值
<= 或 lte :判断左边值是否小于等于右边值
算术运算符
FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+, - , * , / , % 注意:
(1)运算符两边必须是数字
(2)使用+运算符时,如果一边是数字,一边是字符串,就会自动将数字转换为字符串再连接,如:${3 + "5"},结果是:35
FreeMarker中的运算符优先级如下(由高到低排列):

①、一元运算符:!
②、内建函数:?
③、乘除法:*, / , %
④、加减法:- , +
⑤、比较:> , < , >= , <= (lt , lte , gt , gte)
⑥、相等:== , = , !=
⑦、逻辑与:&&
⑧、逻辑或:||
⑨、数字范围:..实际上,我们在开发过程中应该使用括号来严格区分,这样的可读性好,出错少

if逻辑判断(注意:elseif 不加空格)
<#if condition>
...
<#elseif condition2>
...
<#elseif condition3>
...
<#else>
...
</#if>
switch(条件可为数字,可为字符串)
<#switch value>
 <#case refValue1>
....
<#break> 
<#case refValue2> 
....
<#break>
 <#case refValueN>
 ....
<#break>
 <#default>
 ....
 </#switch>
集合 & 循环
//遍历集合
<#list empList! as emp>
    ${emp.name!}    
</#list>

//使用<#break>跳出循环
<#if emp_index = 0><#break></#if>

//集合长度判断 
<#if empList?size!=0></#if> //判断=的时候,注意只要一个=符号,而不是==
<#assign l=0..100/>       // 定义一个int区间的0~100的集合,数字范围也支持反递增,如100..2
<#list 0..100 as i>   // 等效于java for(int i=0; i <= 100; i++)
  ${i}
</#list>

// 截取子集合:
empList[3..5] //返回empList集合的子集合,子集合中的元素是empList集合中的第4-6个元素

// 创建集合:
<#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as x>

// 集合连接运算,将两个集合连接成一个新的集合
<#list ["星期一","星期二","星期三"] + ["星期四","星期五","星期六","星期天"] as x>

// 除此之外,集合元素也可以是表达式,例子如下:
[2 + 2, [1, 2, 3, 4], "whatnot"]

// seq_contains:判断序列中的元素是否存在
<#assign x = ["red", 16, "blue", "cyan"]> 
${x?seq_contains("blue")?string("yes", "no")}    // yes
${x?seq_contains("yellow")?string("yes", "no")}  // no
${x?seq_contains(16)?string("yes", "no")}        // yes
${x?seq_contains("16")?string("yes", "no")}      // no

// seq_index_of:第一次出现的索引
<#assign x = ["red", 16, "blue", "cyan", "blue"]> 
${x?seq_index_of("blue")}  // 2

// sort_by:排序(升序)
<#list movies?sort_by("showtime") as movie></#list>

// sort_by:排序(降序)
<#list movies?sort_by("showtime")?reverse as movie></#list>

// 具体介绍:
// 不排序的情况:
<#list movies as moive>
  <a href="${moive.url}">${moive.name}</a>
</#list>
//要是排序,则用
<#list movies?sort as movie>
  <a href="${movie.url}">${movie.name}</a>
</#list>
    
/ 这是按元素的首字母排序。若要按list中对象元素的某一属性排序的话,则用
<#list moives?sort_by(["name"]) as movie>
  <a href="${movie.url}">${movie.name}</a>
</#list>

//这个是按list中对象元素的[name]属性排序的,是升序,如果需要降序的话,如下所示:
<#list movies?sort_by(["name"])?reverse as movie>
  <a href="${movie.url}">${movie.name}</a>
</#list>
    
//Map对象 创建map
<#assign scores = {"语文":86,"数学":78}>

// Map连接运算符
<#assign scores = {"语文":86,"数学":78} + {"数学":87,"Java":93}>

// Map元素输出
emp.name       // 全部使用点语法
emp["name"]    // 使用方括号

// FreeMarker支持如下转义字符:
\" :双引号(u0022)
\' :单引号(u0027)
\\ :反斜杠(u005C)
\n :换行(u000A)
\r :回车(u000D)
\t :Tab(u0009)
\b :退格键(u0008)
\f :Form feed(u000C)
\l :<
\g :>
\a :&
\{ :{
\xCode :直接通过4位的16进制数来指定Unicode码,输出该unicode码对应的字符.
    
// include指令的作用类似于JSP的包含指令:
<#include "/test.ftl" encoding="UTF-8" parse=true>

// 在上面的语法格式中,两个参数的解释如下:
encoding="GBK"  // 编码格式
parse=true    // 是否作为ftl语法解析,默认是true,false就是以文本方式引入,
注意:在ftl文件里布尔值都是直接赋值的如parse=true,而不是parse="true"
    
//import指令
类似于jsp里的import,它导入文件,然后就可以在当前文件里使用被导入文件里的宏组件
<#import "/libs/mylib.ftl" as my>
上面的代码将导入/lib/common.ftl模板文件中的所有变量,
交将这些变量放置在一个名为com的Map对象中,"my"在freemarker里被称作namespace

数据 + 模板 = 结果

数据 [Java代码]
User user = new User();
user.setName(“张三”);

模板 [HTML简化]
< span >
${user.name}
< /span >

结果
< span >
张三
< /span >

  • Freemarker是免费开源的模板引擎技术
  • Freemarker脚本为(Freemarker Template Language)
  • Freemarker提供了大量内建函数来简化开发

JSP与Freemarker

JSP Freemarker
官方标准
执行方式 编译型 解释型
执行效率
开发效率
扩展能力
数据提取 JSTL + EL 内置标签

前端工程师把数据写在ftl 然后后端工程师拿着名称去添加后台数据

package com.example.json;


import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class FreemarkerSample1 {
    public static void main(String[] args) throws IOException, TemplateException {
        //1. 加载模板
        Configuration confige = new Configuration(Configuration.VERSION_2_3_32);
                     // FreemarkerSample1类所在包中加载ftl所在文件
        confige.setClassForTemplateLoading(FreemarkerSample1.class, "");
        Template t = confige.getTemplate("sample1.ftl");
        //2. 创建数据
        Map<String,Object> data = new HashMap<String, Object>();
        data.put("site","百度");
        data.put("url","https://www.abc.com");
        data.put("data",new Date());
        data.put("number",83719.88321);
        Map info = new HashMap();
        info.put("cpu","i5");
        Computer c1 = new Computer("123456","ALIENWARE",1,"李四",new Date(), 12900f)
        data.put("computer",c1);
        //3. 产生输出    向目标输出
        t.process(data, new OutputStreamWriter(System.out));
    }
}
${site}
${url}
<#--类似于EL表达式-->
Computer.java
public class Computer{
    private String sn; //序列号
    private String mode1; //型号
    private int state; //状态 1-在用 2-闲置 3-报废
    private String user; //使用人
    private Date dog; //采购日期
    private Float price; //购买价格
    private Map info; //电脑配置信息
    ......
}
sample1.ftl
<#-- Freemarker取值 -->
${site}
${url}

<#-- !默认值 -->
${author!"不存在的属性"}

<#-- ?string格式化输出 -->
${date?string("yyyy年MM月dd日 HH:mm:ss SSS")}
${number?string("0.00")}

SN:${computer.sn}
型号:${computer.mode1}
状态:${computer.state}
用户:${computer.user}
采购时间:${computer.dop?string("yyyy年MM月dd日")}
采购价格:${computer.price?string("0.00")}

//对map的提取
CPU:${computer.info["cpu"]}
内存:${computer.info["memory"]!"无内存信息"}

FTL(FreeMarker Template Language)取值

  • ${属性名} 取值,可对属性进行计算

  • ${属性名!默认值} 使用默认值[解决空值异常]
    ${author!”不存在的属性”} => 不存在的属性

  • ${属性名?string} 格式化输出
    ${data?string(“yyyy年MM月dd日 HH:mm:ss SSS”)}

    ${number?string(“0.00”)} 小数部位保留两位

if分支判断
<#if 条件1>
  条件1成立执行代码
<#if 条件2>
  条件2成立执行代码
<#if 条件3>
  条件3成立执行代码
<#else>
  其他情况下执行代码
switch分支判断
<#switch value>
  <#case refValue1>
    ...
    <#break>
  <#case refValue2>
    ...
    <#break>
  <#case refValueN>
    ...
    <#break>
<#default>
...
</#switch>

sample1.ftl
<#-- Freemarker取值 -->
${site}
${url}

<#-- !默认值 -->
${author!"不存在的属性"}

<#-- ?string格式化输出 -->
${date?string("yyyy年MM月dd日 HH:mm:ss SSS")}
${number?string("0.00")}
<#if computer.sn == "1234567">
重要设备
</#if>

SN:${computer.sn}
型号:${computer.mode1}
状态:${computer.state}

<#if computer.state == 1>
状态:正在使用
<#elseif computer.state == 2>
状态:闲置
<#elseif computer.state == 3>
状态:已作废
</#if>

<#if computer.user??> //??代表判断对象是否为空,true不为空,false为空
用户:${computer.user}
</#if>
--------------------------------------------------------
<#switch computer.state>  //同一个属性的不同值进行判断
    <#case1>
        状态:正在使用
        <#break>
    <#case2>
        状态:闲置
        <#break>
    <#case3>
        状态:已作废
        <#break>
    <#default>
        状态:无效状态
--------------------------------------------------------
采购时间:${computer.dop?string("yyyy年MM月dd日")}
采购价格:${computer.price?string("0.00")}

//对map的提取
CPU:${computer.info["cpu"]}
内存:${computer.info["memory"]!"无内存信息"}

list迭代列表

<#list students as stu>
  <li>${stu_index}-${stu.name}</li>
</#list>
list迭代Map
<#list map?keys as key>
  ${key}:${map[key]}
</#list>
FreemarkerSample2.java
package com.example.json;


import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class FreemarkerSample1 {
    public static void main(String[] args) throws IOException, TemplateException {
        //1. 加载模板
        Configuration confige = new Configuration(Configuration.VERSION_2_3_32);
          // FreemarkerSample1类所在包中加载ftl所在文件
        confige.setClassForTemplateLoading(FreemarkerSample2.class, "");
        Template t = confige.getTemplate("sample1.ftl"); //得到模板对象
        //2. 创建数据
        Map<String,Object> data = new HashMap<String,object>();
        List<Computer> computers = new ArrayList();
        computers.add(new Computer("123456","ALIENWARE",2,null,new Date(), 12900f, new HashMap()));
        computers.add(new Computer("123456","HP XXX",1,"张三",new Date(), 12900f, new HashMap()));
        computers.add(new Computer("1233145","DELL XXX",3,"李四",new Date(), 12900f, new HashMap()));
        computers.add(new Computer("1234536","ACER XXX",1,"王五",new Date(), 12900f, new HashMap()));
        computers.add(new Computer("12344456","MSI XXX",1,"赵六",new Date(), 12900f, new HashMap()));
        data.put("computers", computers);
        //LinkedHashMap可以保证数据按存放顺序进行提取
        Map computerMap = new LinkedHashMap();
        for(Computer c : computers){
            computerMap.put(c.getSn(), c);
        } //下方新增
        //3. 产生输出    向目标输出
        t.process(data, new OutputStreamWriter(System.out));
    }
}
sample2.ftl
<#list computers as c> //迭代循环 用变量c
序号:${c_index} //迭代变量 index保存了循环的索引,从0开始 +1则从1开始
SN:${c.sn}
型号:${c.mode1}
<#switch c.state>  //同一个属性的不同值进行判断
    <#case1>
        状态:正在使用
        <#break>
    <#case2>
        状态:闲置
        <#break>
    <#case3>
        状态:已作废
        <#break>
    <#default>
        状态:无效状态
</#switch>
<#if c.user??>
状态:${c.state}
</#if>
用户:${c.user}
采购时间:${c.dop?string("yyyy-MM-dd")}
采购价格:${c.price?string("0.00")}
</#list>
-------------------------------------------------------------
<#list computer_map?keys as k >
${k}-${computer_map[k].model}
${computer_map[k].price?string("0.00")}
</#list>

Freemarker内建函数[看成java代码 就是点”.”换成了问号”?”]

freemarker.foofun.cn 在线学习

函数名 说明 示例
lower_case/upper_case 大小写转换 “abcde”?upper_case
cap_first 首字母大写 “”abcde””?cap_first
index_of 查找字符索引 “abcde”?index_of(“b”)
length 返回字符串长度 “abcde”?length
round/floor/ceiling 四舍五入/下取整/上取整 pi?floor
size 得到集合元素总数 students?size
first/last 获取第一个/最后一个元素 students?first
sort_by 按某个属性对集合排序 list?sort_by(“time”)
example:
Map<String,Object> data = new HashMap<String,Object>();
data.put("name", "jackson");
----------------------------
${name?cap_first}
//违禁字母屏蔽
${words?replace("blood", "******")}
${words?index_of("blood")}
//三目运算 
${(words?index_of("blood") != -1)?string("包含敏感词汇","不包含敏感词汇")}

公司共有${computers?size}台电脑
//集合排序 返回一个新的集合
<#list computers?sort_by("price") as c >
    ${c.sn}-${c.price}
</#list>
//集合排序{降序}
<#list computers?sort_by("price")?reverse as c >
    ${c.sn}-${c.price}
</#list>

Freemarker与Servlet整合

web.xml
<servlet>
  <servlet-name>freemarker</servlet-name>
  <servlet-class>freemarkerServlet</servlet-class>
  <init-param>
      <param-name>TemplatePath</param-name>
    <param-value>/WEB-INF/ftl</param-value>
  </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>freemarker</servlet-name>
  <url-pattern>*.ftl</url-pattern> //地址映射 输入这样的后缀时自动去/WEB-INF/ftl查找
</servlet-mapping>
test.ftl
这是一个测试FTL文件

localhost:8080/fm-web/test.ftl
employee.ftl
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>员工列表</title>
    <link href="./css/bootstrap.css" type="text/css" rel="stylesheet"/>
    <script type="text/javascript" src="./js/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="./js/bootstrap.js"></script>

    <style type="text/css">
        .pagination {
            margin: 0px
        }

        .pagination > li > a, .pagination > li > span {
            margin: 0 5px;
            border: 1px solid #dddddd;
        }

        .glyphicon {
            margin-right: 3px;
        }

        .form-control[readonly] {
            cursor: pointer;
            background-color: white;
        }
        #dlgPhoto .modal-body{
            text-align: center;
        }
        .preview{

            max-width: 500px;
        }
    </style>
    <script>
        $(function () {
            
            $("#btnAdd").click(function () {
                $('#dlgForm').modal()
            });
        })
    </script>
</head>
<body>

<div class="container">
    <div class="row">
        <h1 style="text-align: center">IMOOC员工信息表</h1>
        <div class="panel panel-default">
            <div class="clearfix panel-heading ">
                <div class="input-group" style="width: 500px;">
                    <button class="btn btn-primary" id="btnAdd"><span class="glyphicon glyphicon-zoom-in"></span>新增
                    </button>
                </div>
            </div>

            <table class="table table-bordered table-hover">
                <thead>
                <tr>
                    <th>序号</th>
                    <th>员工编号</th>
                    <th>姓名</th>
                    <th>部门</th>
                    <th>职务</th>
                    <th>工资</th>
                    <th>&nbsp;</th>
                </tr>
                </thead>
                <tbody>
//<c:forEach items="${applicationScope.employees}" var="emp" varStatus="idx">
                <#list employee_list as emp>
                <tr>
                    <td>${emp_index + 1}</td>
                    <td>${emp.empno?string("0")}</td> //整数输出用0初始化
                    <td>${emp.ename}</td>
                    <td>${emp.department}</td>
                    <td>${emp.job}</td>
//<td style="color: red;font-weight: bold">¥<fmt:formatNumber value="${emp.salary}" pattern="0,000.00"></fmt:formatNumber></td>
                    <td style="color: red;font-weight: bold">¥${emp.salary?string("0.00")}</td>
                </tr>
                </c:forEach>
            </table>
        </div>
    </div>
</div>

<!-- 表单 -->
<div class="modal fade" tabindex="-1" role="dialog" id="dlgForm">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title">新增员工</h4>
            </div>
            <div class="modal-body">
                <form action="/untitled_war_exploded/create" method="post" >
                    <div class="form-group">
                        <label for="empno">员工编号</label>
                        <input type="text" name="empno" class="form-control" id="empno" placeholder="请输入员工编号">
                    </div>
                    <div class="form-group">
                        <label for="ename">员工姓名</label>
                        <input type="text" name="ename" class="form-control" id="ename" placeholder="请输入员工姓名">
                    </div>
                    <div class="form-group">
                        <label>部门</label>
                        <select id="dname" name="department" class="form-control">
                            <option selected="selected">请选择部门</option>
                            <option value="市场部">市场部</option>
                            <option value="研发部">研发部</option>
                            <option value="后勤部">后勤部</option>
                        </select>
                    </div>

                    <div class="form-group">
                        <label>职务</label>
                        <input type="text" name="job" class="form-control" id="sal" placeholder="请输入职务">
                    </div>

                    <div class="form-group">
                        <label for="sal">工资</label>
                        <input type="text" name="salary" class="form-control" id="sal" placeholder="请输入工资">
                    </div>

                    <div class="form-group" style="text-align: center;">
                        <button type="submit" class="btn btn-primary">保存</button>
                    </div>
                </form>
            </div>

        </div><!-- /.modal-content -->
    </div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</body>
</html>
Employee.java
public class Employee {
    private Integer empno;
    private String ename;
    private String department;
    private String job;
    private Float salary;
    ...
}
ListServlet.java
package com.imooc.freemarker.servlet;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class ListServlet
 */
@WebServlet("/list")
public class ListServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public ListServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List list = new ArrayList();
        list.add(new Employee(7731,"张三" , "市场部" , "客户代表" , 8000f));
        list.add(new Employee(8871,"李四" , "研发部" , "运维工程师" , 7000f));
        request.getServletContext().setAttribute("employee_list", list);
        request.getRequestDispatcher("/employee.ftl").forward(request, response);
    }
}
阅读全文

XML_Schema dtd Dom4j XPath

2023/9/12

XML(Extensible Markup Language)

XML可扩展标记语言,编写XML就是编写标签,与HTML非常类似,扩展名.xml。有良好的人机可读性

hr.xml
<employee>
 <name>张三</name>
 <age>31</age>
 <height>178</height>
</employee>
  • XML与HTML非常相似,都是编写标签
  • XML没有预定义标签,HTML存在大量预定义标签
  • XML重在保存与传输数据,HTML用于显示信息

XML用途

① 程序中有各种设置项提取出来存放在XML文件中,利用XML进行程序的设计

web.xml - web应用配置文件
<web-app>
 <servlet>
  <servlet-name>InitTest</servlet-name>
  <servlet-class>moreservlets.InitServlet</servlet-class>
   <init-param>
    <param-name>param1</param-name>
    <param-value>value1</param-value>
   </init-param>
   <init-param>
    <param-name>param2</param-name>
    <param-value>2</param-value>
   </init-param>
 </servlet>
</web-app>

② 用于保存程序产生的数据 [简单的变成可以把数据库导成xml]

hr.xml
<employee>
 <name>张三</name>
 <age>31</age>
 <height>178</height>
 <salary>7800</salary>
</employee>

③ 网络间的数据传输

webservice底层soap协议
<Envelop>
<Body>
<m:reversexmlns:m="urn:strings-com:IString">
<theString>Hello,World</theString>
</m:reversexmlns:m>
</Body>
</Envelop>

XML文档结构

  • 第一行必须是XML声明

    XML声明说明XML文档的基本信息,包括版本号与字符集,写在XML第一行

    <?xml version="1.0" encoding="UTF-8"?>
    version代表版本号1.0
    encoding UTF-8设置字符集,用于支持中文
    <!-- 人力资源管理系统 -->
    <hr>
      <employee no="3309">
        <name>张三</name>
        <age>31</age>
        <salary>4000</salary>
        <department>
          <dname>会计部</dname>
          <addresss>XX大厦-B103</addresss>
        </department>
      </employee>
    
      <employee no="3310">
        <name>李四</name>
        <age>23</age>
        <salary>4000</salary>
        <department>
          <dname>工程部</dname>
          <addresss>XX大厦-B104</addresss>
        </department>
      </employee>
    </hr>
    
  • 有且只有一个根节点

  • XML标签的书写规则与HTML相同

合法的标签名

标签名要有意义
建议使用英文,小写字母,单词之间使用”-“分割

适当的注释与缩进

适当的注释与缩进可以让XML文档更容易阅读

合理使用属性

标签属性用于描述标签不可或缺的信息
对标签分组或者为标签设置Id时常用属性表示

<shop-cart>
<item sn="771938" category="电器">
 <name>XXX空调</name>
 <price>2000.00</price>
 <num>1</num>
</item>
<item sn="890321" category="食品">
 <name>法式面包</name>
 <price>10.00</price>
 <num>5</num>
</item>
</shop-cart>
特殊字符与CDATA标签

标签体中,出现”<”、”>”特殊字符,会破坏文档结构

错误的XML
<question> 1+4<3是否正确?</question>

解决方案①:使用实体引用

实体引用 对应符号 说明
& It; < 小于
& gt; > 大于
& amp; & 和号
& apos; 单引号
& quot; 双引号
修改后的XML
<question> 1+4&lt;3是否正确?</question>

解决方案②:使用CDATA标签
CDATA指的是不应由XML解析器进行解析的文本数据
从”< ![CDATA[“开始,到”]] >“结束

<lesson>
<content>
 <![CDATA[
 本节我们来学习html中a标签的使用:
 <body>
  <a href="index.html">首页</a>
 </body>
 ]]>
</content>
</lesson>
有序的子元素

在XML多层嵌套的子元素中,标签前后顺序应保持一致
<…> </…>

XML语义约束之DTD

XML文档结构正确,但可能不是有效的

  • 例如,员工档案XML中绝不允许出现 “植物品种” 标签。XML语义约束就是用于规定XML文档中允许出现哪些元素
  • XML语义约束由两种定义方式:DTDXML Schema

DTD(Document Type Definition, 文档类型定义) 是一种简单易用的语义约束方式
DTD文件的扩展名为.dtd [用于说明HTML中拥有哪些节点可以出现]

hr.dtd
<!ELEMENT hr (employee+)>
<!ELEMENT employee (name,age,salary,department)>
<!ATTLIST employee no CDATA "">
<!ELEMENT name (#PCDATA)>
...
定于hr节点下只允许出现1个employee子节点
<!ELEMENT hr (employee)>

employee节点下必须包含以下四个节点,且按顺序出现
<!ELEMENT employee (name,age,salary,department)
    
定义name标签体只能是文本,#PCDATA代表文本元素
<!ELEMENT name (#PCDATA)>
DTD定义节点数量
  • 如果某个子节点需要多个重复出现,则需要在子节点后增加相应的描述符
    [后面带个+号 最少出现一个]
hr节点下最少出现1个emploee子节点    [+]
<!ELEMENT hr (employee+)>

hr节点下可出现0..n个employee子节点  [*]
<!ELEMENT hr (employee*)>

hr节点下最多出现1个emploee子节点    [?]
<!ELEMENT hr (employee?)>
XML引用DTD文件
  • 在XML中使用**< !DOCTYPE >**标签来引用DTD文件
书写格式:
<!DOCTYPE 根节点 SYSTEM "dtd文件路径">
示例:
<!DOCTYPE hr SYSTEM "hr.dtd">
案例 [XML像是描述 dtd像是约束 ]
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hr SYSTEM "hr.dtd">
<!-- 人力资源管理系统 -->
<hr>
  <employee no="3309">
    <name>张三</name>
    <age>31</age>
    <salary>4000</salary>
    <department>
      <dname>会计部</dname>
      <address>XX大厦-B103</address>
    </department>
  </employee>

  <employee no="3310">
    <name>李四</name>
    <age>23</age>
    <salary>4000</salary>
    <department>
      <dname>工程部</dname>
      <address>XX大厦-B104</address>
    </department>
  </employee>
</hr>
<?xml version="1.0" encoding="UTF-8" ?>
  <!ELEMENT hr (employee+)>
  <!ELEMENT employee (name,age,salary,department)>
  <!--前后顺序必须匹配 一一对应-->
  <!ATTLIST employee no CDATA "">
  <!ELEMENT name (#PCDATA)>
  <!ELEMENT age (#PCDATA)>
  <!ELEMENT salary (#PCDATA)>
  <!ELEMENT department (dname,address)>
  <!ELEMENT dname (#PCDATA)>
  <!ELEMENT address (#PCDATA)>
  <!--里面是纯文本节点 department有两个子节点-->

XML Schema(比dtd更高级)

  • XML Schema比DTD更为复杂,提供了多个功能
  • XML Schema提供了数据类型、格式限定、数据范围等特性
  • XML Schema是W3C标准
<?xml version="1.0" encoding="UTF-8" ?>
<hr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="hr.xsd">
<!-- 人力资源管理系统 -->
<hr>
  <employee no="3309">
    <name>张三</name>
    <age>31</age>
    <salary>4000</salary>
    <department>
      <dname>会计部</dname>
      <address>XX大厦-B103</address>
    </department>
  </employee>

  <employee no="3310">
    <name>李四</name>
    <age>23</age>
    <salary>4000</salary>
    <department>
      <dname>工程部</dname>
      <address>XX大厦-B104</address>
    </department>
  </employee>
</hr>
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
  <element name="hr">
<!--complexType标签含义是复杂节点,包含子节点时必须使用这个标签-->
    <complexType>
      <sequence>
        <!-- 节点最少出现一次  -->
        <element name="employee" minOccurs="1">
          <complexType>
            <sequence>
              <element name="name" type="string"></element>
              <element name="age">
                <simpleType>
                  <restriction base="integer"> <!--给年龄做限定-->
                    <minInclusive value="18"></minInclusive>
                    <maxInclusive value="60"></maxInclusive>
                  </restriction>
                </simpleType>
              </element>
              <element name="department">
                <complexType>
                  <sequence>
                    <element name="dname" type="string"></element>
                    <element name="address" type="string"></element>
                  </sequence>
                </complexType>
              </element>
            </sequence>
          </complexType>
        </element>
      </sequence>
      <!--no在任何employee节点下必须存在-->
      <attribute name="no" type="string" use="required"></attribute>
    </complexType>
  </element>
</schema>

DOM文档对象模型

DOM(Document Object Model)定义了访问和操作XML文件的标准方法,DOM把XML文档作为树结构来看,能够通过DOM树来读写所有元素。

Dom4j[以java的形式解析xml]

Dom4j是一个易用的、开源的库,用于解析XML。它应用于Java平台,具有性能优异、功能强大和极其易用的特点。

  • Dom4j将XML视为Document对象
  • XML标签被Dom4j定义为Element对象
Dom4j对xml的解析读取和遍历

[原则是按照documents根节点和子节点依次类推的顺序对其进行分析、解析、提取]

<?xml version="1.0" encoding="UTF-8" ?>
<!-- 人力资源管理系统 -->
<hr>
  <employee no="3309">
    <name>张三</name>
    <age>31</age>
    <salary>4000</salary>
    <department>
      <dname>会计部</dname>
      <address>XX大厦-B103</address>
    </department>
  </employee>

  <employee no="3310">
    <name>李四</name>
    <age>23</age>
    <salary>4000</salary>
    <department>
      <dname>工程部</dname>
      <address>XX大厦-B104</address>
    </department>
  </employee>
</hr>
package src;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.util.List;

public class HrReader{
  public void readXml() throws DocumentException {
    String file = "D:\\JetBrains\\IdeaProjects\\Test_html\\src\\hr.xml";
    //SAXReader类是读取XML文件的核心类,用于将XML解析后
      SAXReader reader = new SAXReader();
      Document document = reader.read(file);
      //获取XML文档的根节点,即hr标签
      Element root = document.getRootElement();
      //elements方法用于获取指定的标签集合
      List<Element> employees = root.elements("employee");
      for (Element employee : employees){
       /* //element方法用于获取唯一的子节点对象
        Element name = employee.element("name");
        //getText()方法用于获取标签文本
        String empName = name.getText();
        System.out.println(empName);
        */
        System.out.println(employee.elementText("age"));
        System.out.println(employee.elementText("salary"));
        Element department = employee.element("department");
        System.out.println(department.elementText("dname"));
        System.out.println(department.element("address").getText());
        Attribute att = employee.attribute("no"); /*获取对应文本*/
        System.out.println(att.getText());
        System.out.println("====================");
        }
      }

  public static void main(String[] args) throws DocumentException {
    HrReader reader = new HrReader();
    reader.readXml();
  }
}
Dom4j更新(写入)XML
已追加写入的信息
<hr>
 <employee no="3311">
    <name>李铁柱</name>
    <age>28</age>
    <salary>3600</salary>
    <department>
      <dname>人事部</dname>
      <address>XX大厦-B105</address>
    </department>
  </employee>
</hr>
package src;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class HrWriter {
  public void weiteXml(){
    String file = "D:\\JetBrains\\IdeaProjects\\Test_html\\src\\hr.xml";
    SAXReader reader = new SAXReader();
    try {
      Document document = reader.read(file);
      Element root = document.getRootElement();
      //创建employee子节点 全新空子节点
      Element employee = root.addElement("employee");
      employee.addAttribute("no","3311");
      Element name = employee.addElement("name");
      name.setText("李铁柱");
      employee.addElement("age").setText("28");
      employee.addElement("salary").setText("3600");
      Element department = employee.addElement("department");
      department.addElement("dname").setText("人事部");
      department.addElement("address").setText("XX大厦-B105");
      //内存中组织的dom模型重新写入到对应文件中
      Writer writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
      document.write(writer);
      writer.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    HrWriter hrWriter = new HrWriter();
    hrWriter.weiteXml();
  }
}

XPath路径表达式

  • XPath路径表达式是XML文档中查找数据的语言
  • 掌握XPath可以极大的提高在读取数据时的开发效率
  • 学习XPath本质就是掌握各种形式表达式的使用技巧
最常用的基本表达式
表达式 描述
nodename 选取此节点的所有子节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性
路径表达式 结果
bookstore 选取bookstore元素的所有子节点
/bookstore 选取根元素bookstore
注释:假如路径起始于正斜杠(/), 则此路径始终代表到某元素的绝对路径
bookstore/book 选取属于bookstore的子元素的所有book元素
//book 选取所有book子元素,而不管它们在文档中的位置
bookstore//book 选取属于bookstore元素的后代的所有book元素,而不管它们位于bookstore之下的什么位置
//@lang 选取名为lang的所有属性
XPath谓语表达式
路径表达式 结果
/bookstore/book[1] 选取属于bookstore子元素的第一个book元素
/bookstore/book[last()] 选取属于bookstore子元素的最后一个book元素
/bookstore/book[position()<3] 选取最前面的两个属于bookstore元素的子元素的book元素
//title[@lang] 选取所有拥有名为lang的属性的title元素
/bookstore/book[price>35.00] 选取bookstore元素的所有book元素,且其中的price元素的值须大于35.00
/bookstore/book[price>35.00]/title 选取bookstore元素中的book元素的所有title元素,且其中的price元素的值必须大于35.00

XPath实验室 [优先使用 查询数据]

Jaxen介绍
  • Jaxen是一个Java编写的开源的XPath库。这是适合多种不同的对象模型,包括DOM,XOM,dom4j和JDOM
  • Dom4j底层一来Jaxen实现XPath查询
<?xml version="1.0" encoding="UTF-8"?>
<!-- 人力资源管理系统 -->
<hr>
  <employee no="3309">
    <name>张三</name>
    <age>31</age>
    <salary>4000</salary>
    <department>
      <dname>会计部</dname>
      <address>XX大厦-B103</address>
    </department>
  </employee>

  <employee no="3310">
    <name>李四</name>
    <age>23</age>
    <salary>3200</salary>
    <department>
      <dname>工程部</dname>
      <address>XX大厦-B104</address>
    </department>
  </employee>

  <employee no="3311">
    <name>李铁柱</name>
    <age>28</age>
    <salary>3600</salary>
    <department>
      <dname>人事部</dname>
      <address>XX大厦-B105</address>
    </department>
  </employee>
</hr>
package src;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.util.List;

public class XPathTestor {
  public void xpath(String xpathExp){
    String file = "D:\\JetBrains\\IdeaProjects\\Test_html\\src\\hr.xml";
    SAXReader reader = new SAXReader();
    try {
      Document document = reader.read(file);
      //执行xpath表达式 Node不仅查询标签还可以查询属性
      List<Node> nodes = document.selectNodes(xpathExp);
      for (Node node : nodes){
        //转换成常用的Element对象 父类node强转
        Element emp = (Element)node;
        System.out.println(emp.attribute("no"));
        System.out.println(emp.elementText("name"));
        System.out.println(emp.elementText("age"));
        System.out.println(emp.elementText("salary"));
        System.out.println("===========================");
      }
    } catch (DocumentException e) {
      throw new RuntimeException(e);
    }
  }

  public static void main(String[] args) {
    XPathTestor testor = new XPathTestor();
    //单斜杠要按照根目录一级一级搜索
//    testor.xpath("/hr/employee");
    //双斜杠 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
//    testor.xpath("//employee");
//    testor.xpath("//employee[salary<4000]");
//    testor.xpath("//employee[name='李铁柱']");
//    testor.xpath("//employee[@no=3309]");
//    testor.xpath("//employee[1]");
//    testor.xpath("//employee[last()]");
//    testor.xpath("//employee[position()<6]"); 当前位置小于6
//    testor.xpath("//employee[1] | //employee[3]");
  }
}
阅读全文

Java复习款练习题

2023/8/22

Collection集合习题

练习:Collection集合统计元素出现次数

给定以下代码,请定义方法listTest()统计集合中指定元素出现的次数,如”a”: 2,”b”: 2,”c” :1, “xxx”:0

   public static void main(String[] args) {
        Collection<String> list = new ArrayList<>();
        list.add("a");
        list.add("a");
        list.add("b");
        list.add("b");
        list.add("c");
        System.out.println("a:"+listTest(list, "a"));
        System.out.println("b:"+listTest(list, "b"));
        System.out.println("c:"+listTest(list, "c"));
        System.out.println("xxx:"+listTest(list, "xxx"));
    }
    private static int listTest(Collection<String> list, String s){
        int count = 0;
        for (String string : list){
            if (s.equals(string)){
                count++;
            }
        }
        return count;
    }

练习:Collection集合数组转集合

定义一个方法,要求此方法把int数组转成存有相同元素的集合(集合里面的元素是Integer),并返回。

    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6};
        ArrayList<Integer> arrayList = listTest(arr);
        System.out.println(arrayList);
    }
    public static ArrayList<Integer> listTest(int[] arr){
        ArrayList<Integer> list = new ArrayList<Integer>();
        for (Integer a : arr){
            list.add(a);
        }
        return list;
    }

练习:Collection集合集合转数组

定义一个集合,并把集合(集合里面的元素是Integer)转成存有相同元素的数组,并将结果输出在控制台。(可以使用Object[]数组类型接收转换的数组)

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(100);
        list.add(200);
        list.add(300);
        Object[] obj = list.toArray();
        for (int i = 0; i < obj.length; i++) {
            System.out.println(obj[i]);
        }
    }

public Object[] toArray()

以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 因此,调用者可以自由地修改返回的数组。

练习:Collection集合contains()方法使用

定义一个方法listTest(ArrayList < String > a1, String s),要求使用contains()方法判断a1集合里面是否包含s。

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("itcast");
        list.add("itheima");
        list.add("java");
        System.out.println(listTest(list,"Java"));
    }

    private static boolean listTest(ArrayList<String> a1, String s){
        if (a1.contains(s)){
            return true;
        }
        return false;
    }

练习:Collection集合isEmpty()方法的使用

定义一个方法listTest(ArrayList< String > a1), 要求使用isEmpty()判断a1里面是否有元素。

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        System.out.println(listTest(list));
    }
    public static boolean listTest(ArrayList<String>a1){
        if (a1.isEmpty()){
            return true;
        }
        return false;
    }

练习:简述迭代器的实现原理

当遍历集合时,首先通过调用集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。

Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,在调用Iterator的next()方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

练习:Collection集合返回首次出现索引

定义一个方法listTest(ArrayList< Integer > a1, Integer s),要求返回s在a1里面第一次出现的索引,如果s没出现过返回-1。

    public static void main(String[] args) {
        //定义集合,添加数据
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        System.out.println(listTest(list,5));
    }
    private static int listTest(ArrayList<Integer>a1, Integer s){
        for (int i = 0; i < a1.size(); i++) {
            if (a1.get(i).equals(s)){
                return i;
            }
        }
        return -1;
    }

File类&递归&FileFilter习题

练习:检查文件是否存在,文件的创建

描述:检查D盘下是否存在文件a.txt,如果不存在则创建该文件。

操作步骤:
1.使用绝对路径创建对象关联到D盘的a.txt。
2.通过文件对象方法判断文件是否存在。
3.不存在则调用创建文件的方法创建文件。

    public static void main(String[] args) throws IOException {
        File f = new File("D:\\Clash\\aaa.txt");
        if (!f.exists()){
            f.createNewFile();
        }
    }

练习:单极文件夹的创建

描述:在D盘下创建一个名为bbb的文件夹。

操作步骤:
1.创建文件对象指定路径为d:/bbb
2.调用文件对象创建文件夹的方法

    public static void main(String[] args) throws IOException {
        File f = new File("D:\\Clash\\aaa");
        // File f = new File("D:\\Clash\\aaa\\bbb");
        f.mkdir();
        // f.mkdirs(); 创建多级文件夹
    }

练习:删除文件和文件夹

描述:将D盘下a.txt文件删除。将D盘下aaa文件夹删除,要求文件夹aaa是一个空文件夹。

操作步骤:

1.创建文件对象关联路径:d:/a.txt
2.调用文件对象删除文件的方法
3.创建文件对象关联路径:d:/aaa
4 调用文件对象删除文件夹的方法.

    public static void main(String[] args) {
        // 创建文件对象
        File f = new File("d:/a.txt");
        // 删除文件
        f.delete();
        
        // 创建文件夹对象
        File dir = new File("d:/aaa");
        // 删除文件夹
        dir.delete();
    }

获取文件信息:文件名,文件大小,文件的绝对路径,文件的父路径

描述:获取D盘aaa文件夹中b.txt文件的文件名,文件大小,文件的绝对路径和父路径等信息,并将信息输出在控制台。

操作步骤:

    public static void main(String[] args) {
        // 创建文件对象
        File f = new File("D:\\Clash\\a.txt");
        // 获得文件名
        String filename = f.getName();
        // 获得文件大小
        long filesize = f.length();
        // 获得文件的绝对路径
        String path = f.getAbsolutePath();
        // 获得父文件夹路径,返回字符串
        String parentPath = f.getParent();
        // 获得父文件夹路径,返回文件对象
        File parentFile = f.getParentFile();
        // 输出信息
        System.out.println("文件名:" + filename);
        System.out.println("文件大小:" + filesize);
        System.out.println("文件路径:" + path);
        System.out.println("文件父路径:" + parentPath);
        System.out.println("文件父路径:" + parentFile);
    }

练习:文件夹或文件的判断

描述:

1.判断File对象是否是文件,是文件则输出:xxx是一个文件,否则输出:xxx不是一个文件。
2.判断File对象是否是文件夹,是文件夹则输出:xxx是一个文件夹,否则输出:xxx不是一个文件夹。(xxx是文件名或文件夹名)

操作步骤:

1.创建两个文件对象分别关联到不同的文件,比如:d:/a.txt,d:/aaa
2.调用文件对象的判断是否是文件或是否是文件夹的方法
3.获得文件名,根据判断结果输出信息。

    public static void main(String[] args) {
       File f = new File("D:\\Clash\\a.txt");
       if (f.isFile()){
           System.out.println(f.getName() + "是文件");
       }else {
           System.out.println(f.getName() + "不是文件");
       }

       File f2 = new File("D:\\Clash");
       if (f2.isDirectory()){
           System.out.println(f2.getName() + "是文件夹");
       }else{
           System.out.println(f2.getName() + "不是文件夹");
       }
    }

练习:文件夹的获取方式

描述:

获取指定文件夹下所有的文件,并将所有文件的名字输出到控制台。
注意:不包含子文件夹下的文件

操作步骤:

1.创建文件对象关联到指定文件夹,比如:c:/aaa
2.调用文件对象的listFiles方法获得文件数组
3.遍历文件数组将每一个文件的名字输出到控制台

    public static void main(String[] args) {
        File f = new File("D:\\Clash");
        File[] files = f.listFiles();
        for (File file : files){
            System.out.println(file.getName());
        }
    }

List集合&Set集合习题

练习:List接口的特点

简述List接口的特点

★ 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的读取顺序按照11、22、33的顺序完成的
★ 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)
★ 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素

练习:hashCode和equals方法

请简述HashSet去除重复元素的原理

★ 调用被添加元素的hashCode(), 和HashSet中已有元素的hasCode比较是否相同
★ 如果不同,直接存储
★ 如果相同,调用equals方法比较是否相同
★ 不相同,直接存储元素
★ 相同,认为是同一元素,不存储

练习:数据结构

简述常见的数据结构中元素的存储特点

★ 栈:stack,又称堆栈,对元素的存取特点是先进后出。即,存进去的元素,要在后它后面的元素一次取出后,才能取出该元素
★ 队列:queue,简称队,对元素的存取特点是先进先出。即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素
★ 数组:Array,是有序的元素序列,对元素的存储特点是:
1,查找元素快:通过索引,可以快速访问指定位置的元素
2.增删元素慢
(1).指定索引位置增加元素:需要创建一个新数组,将指定元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置
(2).指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中
★ 链表:linkedlist 对元素的存取有如下的特点:
**(1).**多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的 右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
**(2).**查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素。
(3).增删元素快:
增加元素:只需要修改连接下个元素的地址即可。
删除元素:只需要修改连接下个元素的地址即可。

练习:Comparable和Comparator比较器

简述 Comparable Comparator 两个接口的区别

Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能再类中实现compareTo()一次,不能经常修改的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sortArrays.sort 从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

练习:LinkedList方法的使用

根据要求练习LinkedList方法:

(1).基本方法:add, set, get, remove, clear, size等方法;
(2).特有方法:addFirst, addLast, getFirst, getLast, removeFirst, removeLast, push, pop, clear等方法。

(1).基本方法
public static void main(String[] args) {
        // 1.创建LinkedList
        LinkedList<String> arr = new LinkedList<String>();

        // 2.使用add方法添加元素
        arr.add("西门吹雪");
        arr.add("西门吹雪");
        arr.add("西门吹雪");
        arr.add("西门吹风");
        arr.add("西门吹水");

        // 3.使用add方法在指定索引添加元素
        arr.add(2, "西门吹雨");

        // 4.使用set方法修改指定位置索引
        arr.set(0, "东门");

        for (String str : arr) {
            System.out.println(str);
        }
        System.out.println("--------------");
        // 5.使用get方法获取指定索引的元素
        System.out.println(arr.get(1));

        // 6.使用size方法获取集合大小
        System.out.println(arr.size());

        // 7.使用remove方法删除指定索引的元素
        arr.remove(3);

        // 8.使用clear清空集合中的元素
        arr.clear();
        System.out.println(arr);
    }
}
(2).特有方法
public static void main(String[] args) {
        // 1.创建LinkedList
        LinkedList<String> linked = new LinkedList<String>();

        // 2.使用add方法添加元素
        linked.add("周杰伦");
        linked.add("周星驰");
        linked.add("周华健");
        linked.add("周润发");

        // 3.使用addFirst添加元素到集合最前面
        linked.addFirst("周传雄");

        // 4.使用addLast添加元素到集合最后面
        linked.addLast("周渝民");

        System.out.println(linked);

        // 5.使用getFirst获取集合第一个元素
        System.out.println(linked.getFirst());

        // 6.使用getLast获取集合最后一个元素
        System.out.println(linked.getLast());

        // 7.使用removeLast删除集合第一个元素
        String first = linked.removeFirst();
        System.out.println(first);

        // 8.使用removeLast删除集合最后一个元素
        String last = linked.removeLast();
        System.out.println(last);
        System.out.println(linked);


        // 9.使用pop弹出第一个元素
        String p = linked.pop();
        System.out.println(p);

        // 10.使用push在集合开头插入元素
        linked.push("周立波");
        System.out.println(linked);

        // 11.使用clear清空集合
        linked.clear();
        System.out.println(linked);
    }
}

练习:HashSet存储自定义类型

定义人类,包含姓名和年龄属性。创建4个人存储到HashSet中,姓名和年龄相同的人看做同一人不存储。

// 1.定义Person类.包好姓名年龄属性,重写hashCode()和equals()方法
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
ublic class HashSetTest01 {
    public static void main(String[] args) {
        // 2.创建HashSet用于存储Person类型
        HashSet<Person> hashSet = new HashSet<Person>();

        // 3.添加多个Person到HashSet中
        hashSet.add(new Person("王昭君", 21));
        hashSet.add(new Person("西施", 21));
        hashSet.add(new Person("杨玉环", 20));
        hashSet.add(new Person("貂蝉", 19));
        hashSet.add(new Person("杨玉环", 20));
        hashSet.add(new Person("貂蝉", 19));

        // 4.遍历获取HashSet中的内容
        for (Person p : hashSet) {
            System.out.println(p);
        }
    }
}

练习:List集合元素替换

向list集合添加姓名{张三,李四,王五,二丫,钱六,孙七}, 将二丫替换为王小丫

public class ListTest01 {
    public static void main(String[] args) {
        //1.创建List集合对象
        List<String> list = new ArrayList<>();
        //2.存入数据
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("二丫");
        list.add("钱六");
        list.add("孙七");
        //3.遍历集合,找到"二丫",便将其替换为"王小丫"
        //利用普通for循环遍历List集合
        for(int i = 0;i<list.size();i++) {
            //获取当前元素
            String thisName = list.get(i);
            //如果当前元素是"二丫"
            if("二丫".equals(thisName)) {
                //将其改为"王小丫"
                list.set(i, "王小丫");
            }
        }
        System.out.println(list);
    }
}

//   使用增强for获取LinkedHashSet中的元素
        for (String str : list){
           if ("二丫".equals(str)){
                System.out.println(list);
           }
        }

练习:LinkedHashSet基本使用

使用LinkedHashSet存储以下元素:”王昭君”,”王昭君”,”西施”,”杨玉环”,”貂蝉”。使用迭代器和增强for循环遍历LinkedHashSet。

public class LinkedHashSetTest01 {
    public static void main(String[] args) {
        // 1.创建LinkedHashSet
        LinkedHashSet<String> lhSet = new LinkedHashSet<String>();
        // 2.使用add方法添加元素到LinkedHashSet
        lhSet.add("王昭君");
        lhSet.add("王昭君");
        lhSet.add("王昭君");
        lhSet.add("西施");
        lhSet.add("杨玉环");
        lhSet.add("貂蝉");
        // 3.使用迭代器获取LinkedHashSet中的元素
        Iterator<String> iterator = lhSet.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        // 4.使用增强for获取LinkedHashSet中的元素
        System.out.println("---------------------");
        for (String string : lhSet) {
            System.out.println(string);
        }
    }
}

练习:Collections工具类使用

ArrayList集合中有如下内容: {33,11,77,55},使用Collections.sort()对ArrayList集合中的数据进行排序,并打印出排序后的结果

public class CollectionsTest01 {
    public static void main(String[] args) {
        // 1.创建ArrayList
        ArrayList<Integer> arr = new ArrayList<Integer>();

        // 2.使用add方法添加{33,11,77,55}四个元素
        arr.add(33);
        arr.add(11);
        arr.add(77);
        arr.add(55);

        // 3.调用Collections的sort方法,对集合排序
        Collections.sort(arr);

        // 4.使用增强for遍历ArrayList集合
        for (Integer integer : arr) {
            System.out.println(integer);
        }
    }
}

Map集合习题

练习:Map接口的特点

请简述Map 的特点

★ Map每个元素由键与值两部分组成
★ Map键不能重复,每个键对应一个值
★ 键和值可以为null

练习:Entry键值对对象

说出Entry键值对对象遍历Map集合的原理

Map中存放的是两种对象,一种称为Key(键),一种称为value(值),它们在Map中是一一对应关系,这一种对象又称做Map中的一个Entry(项)。Entry将键值对的对应关系封装成了对象。即键值对对象,这样我们在遍历Map集合时,就可以从每个键值对(Entry)对象中获取对应的键与对应的值。

练习:Map接口中的常用方法

请使用Map集合的方法完成添加元素,根据键删除,以及根据键获取值操作。
    public static void main(String[] args) {
        HashMap<String, String> hm = new HashMap<String, String>();
        hm.put("黄晓明", "Baby");
        hm.put("邓超", "孙俪");
        hm.put("李晨", "范冰冰");
        hm.put("大黑牛", "范冰冰");
        String v1 = hm.put("李晨", "白百合");
        String string = hm.get("大黑牛");
        String v2 = hm.remove("大黑牛");
        System.out.println(v2);
        System.out.println(hm);
    }
//  范冰冰  {邓超=孙俪, 李晨=白百合, 黄晓明=Baby}

练习:Map接口中的方法

往一个Map集合中添加若干元素。获取Map中的所有value,并使用增强for和迭代器遍历输出每个value。

public static void main(String[] args) {
        // 1.创建HashMap
        HashMap<String, String> hm = new HashMap<String, String>();

        // 2.使用put添加元素
        hm.put("黄晓明", "Baby");
        hm.put("邓超", "孙俪");
        hm.put("李晨", "范冰冰");
        hm.put("大黑牛", "范冰冰");

        // 3.使用Map的values方法获取到所有的value
        Collection<String> values = hm.values();

        // 4.使用增强for获取每个value
        for (String value : values) {
            System.out.println(value);
        }

        System.out.println("----------------");
        // 5.使用迭代器获取每个value
        Iterator<String> itr = values.iterator();
        while (itr.hasNext()) {
            System.out.println(itr.next());
        }
    }

练习:HashMap存储键是自定义对象是String

请使用Map集合存储自定义数据类型Car做键,对应的价格做值。并使用keySet和entrySet两种方式遍历Map集合。

        // 1.定义汽车类.包含名称和价格属性,重写hashCode和equals方法
    public class Car {
        private String name;
        private String color;
        ...
    }
---------------------------------------------------
    public static void main(String[] args) {
         // 2.创建HashMapkey保存汽车对象,value是汽车价格
        HashMap<Car, Integer> hm = new HashMap<>();

        // 3.添加汽车到HashMap中
        Car c1 = new Car("长安奔奔", "黄色");
        Car c3 = new Car("奇瑞QQ", "黑色");
        Car c2 = new Car("铃木奥拓", "白色");

        hm.put(c1, 10000);
        hm.put(c2, 20000);
        hm.put(c3, 30000);

         // 4.使用keySet方式遍历Map
        Set<Car> keySet = hm.keySet();
        for (Car c : keySet) {
        // 根据key获取value
            Integer value = hm.get(c);
            System.out.println(c.getName() + "," + c.getColor() + " - " + value);
        }

        System.out.println("-------------");

        // 5.使用entrySet方式遍历Map
        Set<Map.Entry<Car, Integer>> entrySet = hm.entrySet();
        for (Map.Entry<Car, Integer> entry : entrySet) {
            Car key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key.getName() + "," + key.getColor() + " - " + value);
        }
    }

public Set<Map.Entry<K,V>> entrySet()
返回此地图中包含的映射的Set视图。 该集合由地图支持,因此对地图的更改将反映在集合中,反之亦然。 如果在集合中的迭代正在进行时修改映射(除了通过迭代器自己的remove操作,或者通过迭代器返回的映射条目上的setValue操作),迭代的结果是未定义的。 该组支持元件移除,即从映射中相应的映射,经由Iterator.removeSet.removeremoveAllretainAllclear操作。 它不支持addaddAll操作。

练习:Map集合的使用(一)

现在有一个map集合如下:
     Map<Integer,String> map = new HashMap<Integer, String>();
     map.put(1, "张三丰");
     map.put(2, "周芷若");
     map.put(3, "汪峰");
     map.put(4, "灭绝师太");

要求:

1.遍历集合,并将序号与对应人名打印。
2.向该map集合中插入一个编码为5姓名为李晓红的信息
3.移除该map中的编号为1的信息
4.将map集合中编号为2的姓名信息修改为”周林”

    public static void main(String[] args) {
        // 1.定义HashMap,编号作为key,姓名作为value
        Map<Integer, String> map = new HashMap<Integer, String>();
        // 2.使用put方法添加元素
        map.put(1, "张三丰");
        map.put(2, "周芷若");
        map.put(3, "汪峰");
        map.put(4, "灭绝师太");
        // 3.使用keySet+增强for迭代map中的元素,并打印
        Set<Integer> keySet = map.keySet();
        for (Integer key : keySet) {
            String value = map.get(key);
            System.out.println(key + " -- " + value);
        }
        // 4.使用put向该map集合中插入一个编码为5姓名为李晓红的信息
        map.put(5, "李晓红");
        // 5.使用remove移除该map中的编号为1的信息
        map.remove(1);
        // 6.使用put将map集合中编号为2的姓名信息修改为"周林"
        map.put(2, "周林");
        System.out.println(map);
    }

练习:Map集合的使用(二)

有2个数组
第一个数组内容为:[黑龙江省,浙江省,江西省,广东省,福建省],
第二个数组为:[哈尔滨,杭州,南昌,广州,福州],将第一个数组元素作为key
第二个数组元素作为value存储到Map集合中。如{黑龙江省=哈尔滨, 浙江省=杭州, …}

    public static void main(String[] args) {
        // 1.定义第一个数组arr1
        String[] arr1 = {"黑龙江省", "浙江省", "江西省", "广东省", "福建省"};
        // 2.定义第二个数组arr2
        String[] arr2 = {"哈尔滨", "杭州", "南昌", "广州", "福州"};

        // 3.创建HashMap,key存放省,value存放市
        HashMap<String, String> hm = new HashMap<>();

        // 4.使用普通for循环遍历arr1
        for (int i = 0; i < arr1.length; i++) {
        // 5.根据索引到arr1中获取到省
            String key = arr1[i];
        // 6.根据索引到arr2中获取到省会城市
            String value = arr2[i];

        // 7.将省和省会城市添加到HashMap中
            hm.put(key, value);
        }
        // 8.输出HashMap中的内容
        System.out.println(hm);
    }

Math类习题

练习:实现字符串123反转

使用字符数组保存原始字符,利用Random类生成随机索引。

        public class Test1 {
            public static void main(String[] args) {
                Scanner scanner = new Scanner(System.in);
                String next = scanner.next();
                System.out.println("录入的字符串:" + next);
                String s = reverseStr(next);
                System.out.println("反转的字符串:"+ s);
            }
        
            public static String reverseStr(String  str){
                String s = "";
                char[] chars = str.toCharArray();
                for (int i = chars.length - 1; i >= 0; i--) {
                    s +=chars[i] ;
                }
                return s;
            }
        }

toCharArray() 方法将字符串转换为字符数组

练习:键盘录入QQ号判断正确性

必须是5-12位数字,0不能开头

public class Test1 {
    public static void main(String[] args) {
        //1.键盘输入一个qq号码字符串
        Scanner sc = new Scanner(System.in);
        String qq = sc.next();
        //2.调用checkQQ (String qq)方法内实现验证。
        boolean isOK = checkQQ(qq);
        //3.打印验证的结果
        System.out.println("这个QQ号码是否正确:" + isOK);
    }

    /*
     * 定义方法:checkQQ (String qq)方法内实现验证
     * 指定方法的名称:checkQQ
     * 指定方法的参数:String qq
     * 指定方法的返回值:boolean
     */
    public static boolean checkQQ(String qq) {
        //1.验证字符串的长度5-12位之间;
        if (qq.length() < 5 || qq.length() > 12) {
            return false; //说明qq号码的长度不正确
        }
        //2.验证首位字符不能是字符0;只能是字符'1'--'9'
        if (qq.charAt(0) == '0') {
            return false;
        }
        //3.验证字符串中的每个字符都必须是数字字符‘0’-‘9’之间的字符
        for (int i = 0; i < qq.length(); i++) {
            char ch = qq.charAt(i);
            //判断字符是否在 数字字符‘0’-‘9’之间的字符
            if (ch < '0' || ch > '9') {
                return false;//说明qq号码中含有非数字字符
            }
        }
        //4.上述验证都通过了,说明qq号码是正确的
        return true;
    }
 }

练习:大小写字符转换并统计次数

键盘录入一个大字符串,再录入一个小字符串。统计小字符串在大字符串中出现的次数。

   /*
* 分析以下需求,并用代码实现
1.键盘录入一个大字符串,再录入一个小字符串
2.统计小字符串在大字符串中出现的次数
3.代码运行打印格式:
请输入大字符串:woaiheima,heimabutongyubaima,wulunheimahaishibaima,zhaodaogongzuojiushihaoma
请输入小字符串:heima
控制台输出:小字符串heima,在大字符串woaiheima,heimabutongyubaima,wulunheimahaishibaima,zhaodaogongzuojiushihaoma中共出现3次
             */

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 1.键盘录入一个大字符串,再录入一个小字符串
        System.out.print("请输入大字符串:");
        String big = sc.nextLine();
        System.out.print("请输入小字符串:");
        String small = sc.nextLine();
        // 2.统计小字符串在大字符串中出现的次数
        int count = getCount(big, small);
        // 3.代码运行打印格式:
        System.out.println("小字符串" + small + ",在大字符串中共出现" + count + "次");
    }

    /*
     * 方法功能:统计小字符串在大字符串中出现的次数
     * 参数:big 代表大字符串
     * 参数:small 代表小字符串
     * 返回值:小字符串在大字符串中出现的次数
     */
    public static int getCount(String big, String small) {
        int index = 0;
        int count = 0;
        /*
         * indexOf(String str, int fromIndex)
         * 该方法作用:从fromIndex位置开始查找,字符串str第一次出现的位置;若没找到,放回-1
         */
        while ((index = big.indexOf(small, index)) != -1) {
            index++;
            count++;
        }
        return count;
    }

练习:随机小数保留两位

生成一个随机100内小数,转换为保留两位小数的字符串,不考虑四舍五入的问题。

    public static void main(String[] args) {
        double random = Math.random()*100;
        System.out.println("随机数为:");
        System.out.println(random);
        String str = random + " ";
        int index = str.indexOf(".");
        String substring = str.substring(0, index + 3);
        System.out.println("转换为");
        System.out.println(substring);
    }
// substring(int strat, int end)中第一个参数是开始位置,第二个参数是结束位置.

练习:筛选字符串

定义ArrayList集合,存入多个字符串。长度大于5的字符串,打印删除后的集合。

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("bca");
        list.add("dadfa");
        list.add("dddaaa");
        list.add("你好啊");
        list.add("我来啦,你干嘛呢");
        list.add("别跑啊");
        System.out.println("源字符串:");
        System.out.println(list);
        delStrsFromList01(list);
    }
    private static void delStrsFromList01(ArrayList<String> list){
        ArrayList<String> list2 = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            String str = list.get(i);
            if (str.length() > 3){
                list2.add(str);
            }
        }
        for (Object str : list2){
            list.remove(str);
        }
        System.out.println("新字符:" + list);
    }

练习:回文字符串

判断回文字符串。如果一个字符串,从前向后读和从后向前读,都是一个字符串,称为回文串,比如mom,dad,noon。

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        boolean p = isP(next);
        System.out.println(
                "回文数:" + p
        );
    }

    public static boolean isP(String str) {
        int start = 0;
        int end = str.length() - 1;
        while (start < end) {
            if (str.charAt(start) != str.charAt(end)) {
                return false;
            }
            start++;
            end--;
        }
        return true;
    }

charAt() 方法用于返回指定索引处的字符。索引范围为从 0 到 length() - 1。

练习:模拟简单计算器

模拟简单计算器,可以运算+,—,*,/,%。

  • 接收三个参数,一个整数,一个运算符,另一个整数。( ‘5’ ‘+’ ‘7’ )
  • 计算出运算结果。
  • 无法运算时,返回null。
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int a = scanner.nextInt();
        String next = scanner.next();
        int b = scanner.nextInt();
        String count = count(a, next, b);
        System.out.println(a +next +b +"="+count);
    }

    public static String count(int a, String op , int b ){
        int r=0;
        if ("+".equals(op)){
            r = a+b;
        }else  if ("-".equals(op)){
            r = a-b;
        }else  if ("*".equals(op)){
            r = a*b;
        }else  if ("/".equals(op)){
            r = a/b;
        }else  if ("%".equals(op)){
            r = a%b;
        }else {
            return null;
        }
        return r+"";
    }

练习:密码是否合法

校验密码是否合法。合法返回true

  • 必须至少8个字符。
  • 必须至少2个大写字符。超越两个就要依次遍历 for循环
  • 必须只有字母和数字。
    public static void main(String[] args) {
        String s = "qweRY123";
        System.out.println(s+"密码是否合法"+isTrue(s));
    }
    private static boolean isTrue(String s){
        if (s.length()<8){
            return false;
        }

        int countA = 0;
        char[] chars = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            char ch = chars[i];
            //2个大写字母
            if (ch >= 'A' && ch <= 'Z'){
                countA++;
            }
            //字母数字
            if ((ch < '0'|| ch>'9') && (ch < 'A'|| ch>'Z')&&(ch < 'a'|| ch>'z')) {
                return false;
            }
        }
        if (countA < 2){
            return false;
        }
        return true;
    }

练习:模拟用户登录

模拟用户登录。

  • 定义用户类,属性为用户名和密码。
  • 使用集合存储多个用户对象。
  • 录入用户和密码,对比用户信息,匹配成功登录成功,否则登录失败。
  • 登录失败时,当用户名错误,提示没有该用户。
  • 登录失败时,当密码错误时,提示密码有误。

jack-1234 rose-5678 tom-0000
请输入用户名:rose
请输入密码:5678
登录结果:登录成功

public class User {
    private String username;
    private String pwd;
}
------------------------------------------
public class Test {
    static ArrayList<User> list = new ArrayList<>();
    static {
        list.add(new User("jack", "1234"));
        list.add(new User("rose", "5678"));
        list.add(new User("tom", "0000"));
        for (int i = 0; i < list.size(); i++) {
            list.get(i).show();
        }
    }
    public static void main(String[] args) {
        
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = sc.nextLine();
        System.out.println("请输入密码:");
        String password = sc.nextLine();
        User u = new User(username, password);
        String login = login(u);
        System.out.println("登录结果:" + login);
    }
    public static String login(User user) {
        String msg = "";
        String n = user.getUsername();
        String p = user.getPwd();
        for (int i = 0; i < list.size(); i++) {
            User u = list.get(i);
            String name = u.getUsername();
            String pwd = u.getPwd();
            if (name.equals(n)){
                if (pwd.equals(p)){
                    return "登录成功";
                }else {
                    return "密码错误";
                }
            }else {
                msg = "用户名不存在";
                continue;
                }
            }
        return msg;
        }
    }

static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。[ 在此代码内适用于login方法中的list.size() ]
static代码块只在类加载时执行,类是用类加载器来读取的,类加载器是带有一个缓存区的,
它会把读取到的类缓存起来,所以在一次虚拟机运行期间,一个类只会被加载一次,这样的话静态代码块只会运行一次

Object类&Date类&Calender(日期)类&StringBuilder类

练习:简述String和Object中的equals

简述String类中的equals方法 与 Object类中的equals方法的不同点

String类中的equals方法是用来判断两个对象的内容是否相同,而Object 类中的equals方法是用来判断两个对象是否是同一个对象,所谓同一个对象指的是内存中的同一块存储空间。

练习:Object类的toString方法

    public class ToStringTest{
        static int i = 1;
        public static void main(String args[]){
            System.out.println("love " + new ToStringTest());//love java
            ToStringTest a = new ToStringTest();
            a.i++;
            System.out.println("me " + a.i);//me 2
        }
        public String toString(){
            System.out.print("I ");//I
            return "java ";
        }
    }

运行结果:I love java me 2
原因:当执行代码的时候,首先加载静态变量,然后执行main方法,由于main方法内部第一行代码为输出语句,里面new了此类对象,当执行此行代码时会先创建了本类的对象,由于此类重写了toString方法,会先执行toString方法的打印输出,然后返回“java ”,再执行main方法第一行打印输出。在Java中“System.out.println(类对象名);”实际输出的是该对象的toString()方法返回的字符串,即括号中的内容等价于类对象名.toString(),toString方法的好处是在碰到println方法的时候会被自动调用,不用显示的写出来。

练习:Object类equals方法

看下列程序,不运行说结果,写出答案后,并在IntelliJ IDEA中运行看看自己给的答案与运行结果是否正确,并分析原因。

    (1)
        String s1 = new String("abc");
        String s2 = "abc";
        System.out.println(s1 == s2);         //false
        System.out.println(s1.equals(s2));  //true
    (2)
        String s1 = "abc";
              String s2 = "abc";
        System.out.println(s1 == s2);         //true
        System.out.println(s1.equals(s2));     //true
    (3)
        String s1 = "a" + "b" + "c";
              String s2 = "abc";
        System.out.println(s1 == s2);        //true
        System.out.println(s1.equals(s2));     //true
    (4)
        String s1 = "ab";
             String s2 = "abc";
             String s3 = s1 + "c";
        System.out.println(s3 == s2);             //false
              System.out.println(s3.equals(s2));  //true

练习:StringBuilder类与String类的区别

简述StringBuilder类与String类的区别

String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象,所以经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响

StringBuilder又称为可变字符序列,是JDK5.0中新增加的一个类,它是一个类似于String的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。即它是一个容器,容器中可以装很多字符串,并且能够对其中的字符串进行各种操作。它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容,StringBuilder会自动维护数组的扩容

练习:Date类的使用

获取当前的日期, 并把这个日期转换为指定格式的字符串, 如2088-08-08 08:08:08

    public static void main(String[] args) {
        //获取当前日期对象 now;
        Date now = new Date();
        //创建SimpleDateFormat对象 df,并制定日期格式
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //调用df的format(Date  date) 方法,传入now; 接收返回的字符串
        String datestr = df.format(now);
        //打印这个字符串
        System.out.println(datestr);
    }

练习:DateFormat类方法的使用

使用SimpleDateFormat类,把2018-03-04转换为2018年03月04日

    public static void main(String[] args) throws ParseException {
        //创建SimpleDateFormat对象df1,指定日期模式为yyyy-MM-dd
        SimpleDateFormat df1 = new SimpleDateFormat("yyyy-MM-dd");
        //调用df1的parse(String str)方法传入2018-03-04,得到对应日期类型
        Date date = df1.parse("2018-03-04");
        //创建日期格式化对象df2,在获取格式化对象时可以指定风格
        DateFormat df2 = new SimpleDateFormat("yyyy年MM月dd日");
        //调用df2的format(Date date) 传入刚才转换的日期
        String str = df2.format(date);
        System.out.println(str);
    }

public Date parse(String text, ParsePosition pos)
从字符串中解析文本,产生一个Date

练习:Calendar类方法的使用

用程序判断2018年2月14日是星期几。

public static void main(String[] args) {
        //创建Calendar对象
        Calendar c = Calendar.getInstance();
        //将给定的日历字段设置到Calendar对象中
        c.set(Calendar.YEAR, 2018);
        c.set(Calendar.MONTH, 1);
        c.set(Calendar.DATE, 14);
        //设置年
        int year = c.get(Calendar.YEAR);
        //设置月
        int month = c.get(Calendar.MONTH)+1;
        //设置日
        int date = c.get(Calendar.DATE);
        //设置星期
        char week = getWeek(c.get(Calendar.DAY_OF_WEEK));
        //输出结果
        System.out.println(year+"年"+month+"月"+date+"日是星期"+week);
    }
    //定义方法,获取星期汉字
    public static char getWeek(int a){
        char[] c = {' ','日','一','二','三','四','五','六'};
        return c[a];
    }
}

Random类&ArrayList集合习题

练习:随机验证码

  • 随机生成十组六位字符组成的验证码。
  • 验证码由大小写字母、数字字符组成。

开发提示:使用字符数组保存原始字符,利用Random类生成随机索引。

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            String s = verifyCode();
            System.out.println("随机验证码:" + s);
        }
    }
    public static String verifyCode(){
        char[] arr = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
        Random random = new Random();
        String code = "";
        for (int i = 0; i < 6; i++) {
            int index = random.nextInt(arr.length);
            code  += arr[index];
        }
        return code;
    }

练习:输入学生信息保存到集合

键盘录入学生信息,保存到集合中。

  • 循环录入的方式,1:表示继续录入,0:表示结束录入。
  • 定义学生类,属性为姓名,年龄,使用学生对象保存录入数据。
  • 使用ArrayList集合,保存学生对象,录入结束后,遍历集合。
public class Student {
    private String name;
    private int age;
     public void show(){
        System.out.println("姓名: "+ name + " " + "年龄: " + age);
    }
}
----------------------------------
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            ArrayList<Student> list = new ArrayList<>();
            while (true) {
                System.out.println("1.录入信息 0.退出");
                int i = scanner.nextInt();
                switch (i) {
                    case 1:
                        inputStu(list , scanner);
                        break;
                    case 0:
                        System.out.println("录入完毕");
                }
                if (i == 0){
                    break;
                }
            }
    
            for (int i = 0; i < list.size(); i++) {
                Student student = list.get(i);
                student.show();
            }
        }
    
        private static void inputStu(ArrayList<Student> list , Scanner sc) {
            System.out.println("请输入姓名:");
            String name = sc.next();
            System.out.println("请输入年龄:");
            int age = sc.nextInt();
            Student student = new Student(name, age);
            list.add(student);
        }
    }

练习:随机数 次数打印

统计数字出现次数。

  • 定义getNumList方法,随机生成100个数字,数字范围从1到10。
  • 定义printCount方法,统计每个数字出现的次数并打印到控制台。
    public class Test4 {
        public static void main(String[] args) {
            ArrayList<Integer> numList = getNumList();
            //  统计字符数组中字母出现次数
            printCount(numList);
        }
        public static void printCount(ArrayList<Integer> list) {
            int[] count = new int[10];
            // 对应保存数字出现的次数
            for (int i = 0; i < list.size(); i++) {
                int c = list.get(i);
                count[c-1]++;
            }
            // 打印数字和次数
            for (int i = 0 ; i < count.length; i++) {
                    System.out.println("数字:"+(i+1) + "--" + count[i]+"次");
            }
        }
        public static ArrayList<Integer> getNumList() {
            ArrayList<Integer> list = new ArrayList<>();
            Random r = new Random();
            for (int i = 0; i < 100; i++) {
                int x = r.nextInt(10) + 1;
                list.add(x);
            }
            return list;
        }
    }    

练习:需求实现

模拟统计班级考试分数分布情况,分别统计100-80,79-60,59-40,39-0各个阶段的人数。

  • 定义getScoreList方法,随机生成50个数字,数字范围从0到100。
  • 定义countScore方法,统计各个阶段的分数个数。
  • 定义printCount方法,打印各个阶段的统计结果。
    public static void main(String[] args) {
        ArrayList<Integer> scoreList = getScoreList(); //获取随机分数
        ArrayList<Integer> countList = countScore(scoreList); //定义计数的变量
        printCount(countList);
    }
    public static ArrayList<Integer> countScore(ArrayList<Integer> scoreList) {
        ArrayList<Integer> countList = new ArrayList<>();
        int count100 = 0;
        int count79 = 0;
        int count59 = 0;
        int count39 = 0;

        for (int i = 0; i < scoreList.size(); i++) {
            Integer score = scoreList.get(i);
            if (score <= 100 && score >= 80) {
                count100++;
            } else if (score <= 79 && score >= 60) {
                count79++;
            } else if (score <= 59 && score >= 40) {
                count59++;
            } else {
                count39++;
            }
        }

        countList.add(count100);
        countList.add(count79);
        countList.add(count59);
        countList.add(count39);

        return countList;
    }

    private static ArrayList<Integer> getScoreList(){
        ArrayList<Integer> list = new ArrayList<>();
        Random r = new Random();
        for (int i = 0; i < 50; i++) {
            int x = r.nextInt(100);
            list.add(x);
        }
        return list;
    }

    private static void printCount(ArrayList<Integer> countList) {
        int start = 100;
        int end = 80;
        for (int i = 0; i < countList.size(); i++) {
            Integer integer = countList.get(i);
            System.out.println(start + "\t分 --" + end + " \t分:" + integer+"人");
            if (i == 0){
                start-=21;
                end -=20;
            }else if (i == countList.size()-2){
                start-=20;
                end-=40;
            }else {
                start -= 20;
                end -= 20;
            }
        }
    }

练习:添加移除展示元素

自定义MyList类,实现存取元素的功能。

  • 定义add方法,可以保存元素,添加MyList尾部。
  • 定义remove方法,可以获取到最后添加的元素,并从MyList中移除该元素。
  • 定义show方法,可以展示MyList中的元素。
public static void main(String[] args) {
        MyList myList = new MyList();
        for (int i = 0; i < 3; i++) {
            myList.add(i);
        }
        System.out.println("添加元素后:");
        myList.show();

        Integer remove = myList.remove();
        System.out.println("获取元素:");
        System.out.println(remove);
        System.out.println("获取元素后:");
        myList.show();
    }
}

class MyList {
    ArrayList<Integer> ml = new ArrayList<>();

    public void add(Integer i) {
        ml.add(i);
    }

    public Integer remove() {
        Integer remove = ml.remove(ml.size() - 1);
        return remove;
    }

    public void show() {
        System.out.println(ml);
    }
}

线程&同步习题

练习:多线程开启

请描述Thread类中的start()方法与run()方法的区别

线程对象调用run()方法不开启线程,仅是对象调用方法。
线程对象调用start()方法开启线程,并让jvm调用run()方法在开启的线程中执行。

练习:创建多线程

请描述创建线程的两种方法
  • 将类声明为Thread的子类

①.定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
②.创建Thread子类的实例,即创建了线程对象
③.调用线程对象的start()方法来启动该线程

  • 声明一个类实现Runnable接口

①.定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体
②,创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,Thread对象才是真正的线程对象
③.调用线程对象的start()方法来启动线程

练习:多线程

请编写程序,分别打印主线程的名称和子线程的名称

要求使用两种方式实现:
第一种方式:继承Thread类。
第二种方法:实现Runnable接口。

操作步骤描述

  • 继承Thread类

①.定义一个子线程的类,继承Thread类
②.在子线程类中重写run方法,在run方法中打印子线程的名称
③.定义一个测试类
④.在main方法中打印主线程的名称
⑤.在main方法中创建子线程对象
⑥.调用子线程对象的start方法,开启子线程

// 1.定义一个子线程的类,继承Thread类;
public class SubThread extends Thread{
// 2.在子线程类中重写run方法,在run方法中打印子线程的名称;
    public void run(){
    // 打印子线程的名称
      System.out.println("subThread:" + Thread.currentThread().getName());
    }
}
// 3.定义一个测试类
public class ThreadDemo{
    public static void main(String[] args){
// 4.在main方法中打印主线程的名称;
    System.out.println("main:" + Thread.currentThread().getName());
// 5.在main方法中创建子线程对象;
    SubThread st = new SubThread();
// 6.调用子线程对象的start方法,开启子线程。
    st.start();
    }
}
  • 实现Runnable接口

①.定义一个子任务类,实现Runnable接口
②.在子任务中重写run方法,在run方法中打印子线程的名称
③.定义一个测试类
④.在main方法中打印主线程的名称;
⑤.在main方法中创建一个子任务对象;
⑥.在main方法中创建一个Thread类的对象,并把子任务对象传递给Thread类的构造方法;
⑦.调用Thread类对象的start方法开启子线程;

// 1.定义一个子任务类,实现Runnable接口。
public class SubRunnable implements Runnable{
    @Override
    public void run() {
// 2.在子任务类中重写run方法,在run方法中打印子线程的名称。
    System.out.println("SubRunnable:"+ Thread.currentThread().getName());
// 3.定义一个测试类。
   }
}
public class RunnableDemo {
    public static void main(String[] args) {
// 4.在main方法中打印主线程的名称。
    System.out.println("RunnableDemo:"+ Thread.currentThread().getName());
// 5.在main方法中创建一个子任务对象。
    SubRunnable r = new SubRunnable();
// 6.在main方法中创建一个Thread类的对象,并把子任务对象传递给Thread类的                         构造方法。
    Thread t = new Thread(r);
// 7.调用Thread类对象的start方法开启子线程。
    t.start();
 }
}

练习:实现Runnable接口的优势

请描述实现Runnable接口比继承Thread类所具有的优势:

①.适合多个相同的程序代码的线程去共享同一个资源
②.可以避免java中的单继承的局限性
③.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
④.线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread的类

练习:多线程

创建多线程对象,开启多线程。在子线程中输出1-100之间的偶数,主线程输出1-100之间的奇数

自定义线程类

public class MyThread extends Thread {
    /**
     * 重写run方法,完成该线程执行的逻辑
     */
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println("子线程打印输出偶数:" + i);
            }
        }
    }
    public class Test11 {
        public static void main(String[] args) {
            //创建自定义线程对象
            MyThread mt = new MyThread();
            //开启线程
            mt.start();
            //在主方法中执行for循环
            for (int i = 1; i <= 100; i++) { 
                if (i % 2 == 1) {
                    System.out.println("主线程打印输出奇数:" + i);
                }
            }
        }
    }

练习:线程状态

请描述在线程的生命周期中, 有几种状态呢 ?

1.NEW(新建) 线程刚被创建,但是并未启动。

2.Runnable(可运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。

3.Blocked(锁阻塞)
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。

4.Waiting(无限等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。

5.Timed Waiting(计时等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。

6.Teminated(被终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

线程池&lambda表达式习题

练习:线程池概念

请描述什么是线程池

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复用户创建线程而消耗过多的资源

练习:线程池优点

请描述合理利用线程池能够带来的三个好处

1,降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可以执行多个任务
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

练习:Lambda表达式

请列举Lambda语法的省略规则

在Lambda标准格式的基础上,使用省略句写法的规则为:
1.小括号内参数的类型可以省略
2.如果小括号内有且只有一个参数,则小括号可以省略
3.如果大括号内有且只有一个语句,则无论是否有返回值,都可以省略大括号、return、关键字以及语句分号

练习:Lambda表达式

请列举Lambda表达式的3个组成部分,并解释说明

Lambda标准格式Lambda省去面向对象的条条框框,格式由3个部分组成:一些参数、一个箭头、一段代码
Lambda表达式的标准格式
1.小括号内的语法与传统方法参数列表一致:无参数则留空,多个参数则用逗号分割
2.”—>”是新引入的语法格式,代表指向动作
3.大括号内的语法与传统方法体要求基本一致

练习:Lambda表达式

请描述Lambda的使用前提

Lambda的语法非常简介,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
1.使用Lambda必须具有接口,且要求接口中有且只有一个抽象方法。无论是JDK内置的Runnable、Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才能使用Lambda。
2.使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例

练习:多线程

代码实现打印输出1-99
public class Test06 {
    public int start = 1;
    public int end = 99;

public static void main(String[] args) {
    new Test06().method();
 }

public void method() {
    //插入代码处
         Runnable a = () -> {
      for (int i = start; i <end; i++) {
        System.out.println(i);
      }
    };
      Thread t = new Thread(a);
      t.start();
     }
}

练习:多线程

请问该程序的运行结果是什么? 如有问题,请说明原因。
public class Test07implements Runnable {
    public static void main(String[] args) {
      Thread t = new Thread(new Test07());
      t.start();
      }

public void run(int num) {
    for (int i = 0; i < num; i++) {
       System.out.println(i);
    }
  }
}

在编译时期就会报错

​ Test类没有重写Runnable接口中的run()方法

​ public void run(int num)不是Runnable接口中的run()方法。

注意:Runnable接口中的run()方法,参数列表为空,不带参数。

练习:线程池练习

使用线程池创建多线程。模拟同学找老师学习Java。

1.创建线程池对象,包含2个线程。从线程池中获取线程对象,然后调用MyRunnable中的run()。
2.在MyRunnable实现类中,首先在控制台打印需求,“我需要一个老师”。模拟需要2秒钟时间老师可以过来指导学生,并在控制台打印老师的姓名。最后,在控制台打印“教我java,教完后,老师回到了办公室”;

class Test implements Runnable{
    @Override
    public void run() {
        System.out.println("我要一个老师");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("老师来了" + Thread.currentThread().getName());
        System.out.println("教我Java,教完后,老师回到了办公室");
    }
}

class ThreadPoolDemo{
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(2);
        Test r = new Test();
        service.submit(r);//线程1
        service.submit(r);//线程2
        service.submit(r);//线程3
    }
}

练习:Lambda(无参无返回)

给定一个导演 Director接口,内含唯一的抽象方法makeMovie,且无参数、无返回值,使用lambda表达式在Test中完成调用。

interface Director{
    void makeMovie();
}
public class Test {
    public static void main(String[] args) {
        // TODO 请使用Lambda【省略格式】调用invokeDirect方法
        invokeDirect(()-> System.out.println("拍电影啦"));
    }
    private static void invokeDirect(Director director){
        director.makeMovie();
    }
}

练习:Lambda(有参有返回)

给定一个计算器 Calculator 接口,内含抽象方法 calc (减法),其功能是可以将两个数字进行相减,并返回差值。使用Lambda表达式在Test中完成调用

interface Calculator{
    int calc(int a, int b);
}
public class Test {
    public static void main(String[] args) {
        // TODO 请分别使用Lambda【标准格式】及【省略格式】调用invokeCalc方法来计算130-120的结果ß
        invokeCalc(130, 120, (int a, int b) -> {
            return a - b;
        }); //标准格式
        invokeCalc(130,120,(a,b)-> a-b); //省略模式
    }
    private static void invokeCalc(int a, int b, Calculator calculator){
        int result = calculator.calc(a,b);
        System.out.println("结果是:" + result);
    }
}

Stream流

练习一:Pedicate接口使用

请在测试类main方法中完成以下需求

已知有Integer[] arr = {-12345, 9999, 520, 0,-38,-7758520,941213}

a) 使用lambda表达式创建Predicate对象p1,p1能判断整数是否是自然数(大于等于0)

b) 使用lambda表达式创建Predicate对象p2,p2能判断整数的绝对值是否大于100

c) 使用lambda表达式创建Predicate对象p3,p3能判断整数是否是偶数

遍历arr,仅利用已创建的Predicate对象(不使用任何逻辑运算符),完成以下需求

​ i. 打印自然数的个数

​ ii. 打印负整数的个数

​ iii. 打印绝对值大于100的偶数的个数

​ iv. 打印是负整数或偶数的数的个数

public static void main(String[] args) {
        Integer[] arr = {-12345, 9999, 520, 0,-38,-7758520,941213};

        //a)   使用lambda表达式创建Predicate对象p1,p1能判断整数是否是自然数
        Predicate<Integer> p1 = (s) -> s>=0;
        //b)   使用lambda表达式创建Predicate对象p2,p2能判断整数的绝对值是否大于100
        Predicate<Integer> p2 = (s) -> Math.abs(s)>100;
        //c)   使用lambda表达式创建Predicate对象p3,p3能判断整数是否是偶数
        Predicate<Integer> p3 = (s) -> s%2==0;

        //e)   遍历arr,仅利用已创建的Predicate对象(不使用任何逻辑运算符),完成以下需求
        int count1 = 0;
        int count2 = 0;
        int count3 = 0;
        int count4 = 0;
        for (Integer i : arr) {
            //统计自然数个数
            if (p1.test(i)){
                count1++;
            }
            //统计负整数个数
            if (p1.negate().test(i)){
                count2++;
            }
            //统计绝对值大于100的偶数个数
            if (p2.and(p3).test(i)){
                count3++;
            }
            //统计是负整数或偶数的数的个数
            if (p1.negate().or(p3).test(i)){
                count4++;
            }
        }
        //分别打印结果
        System.out.println("自然数的个数为:"+count1);
        System.out.println("负整数的个数为:"+count2);
        System.out.println("绝对值大于100的偶数的个数为:"+count3);
        System.out.println("是负整数或偶数的数的个数为:"+count4);
    }

练习:Function接口使用

[这是一个功能界面,因此可以用Lambda表达式或方法引用的赋值对象]

Interface Function<T,R>
T:函数输入的类型
R:函数结果的类型

Interface Map<K,V>
K:由此地图维护的键的类型
V:映射值的类型

1.使用lambda表达式分别将以下功能封装到Function对象中

a) 求Integer类型ArrayList中所有元素的平均数

b) 将Map < String,Integer > 中value存到ArrayList < Integer >中

2.已知学生成绩如下

姓名 成绩
岑小村 59
谷天洛 82
渣渣辉 98
蓝小月 65
皮几万 70

3.以学生姓名为key成绩为value创建集合并存储数据,使用刚刚创建的Function对象求学生的平均成绩

    public static void main(String[] args) {
        //1.   使用lambda表达式分别将以下功能封装到Function对象中
        //a)   求Integer类型ArrayList中所有元素的平均数
        Function<ArrayList<Integer>,Integer> f1 = (list)->{
            Integer sum = 0;
            for (Integer i : list) {
                sum+=i;
            }
            return sum/list.size();
        };

        //b)   将Map<String,Integer>中value存到ArrayList<Integer>中
        Function<Map<String,Integer>,ArrayList<Integer>> f2 = (map)->{
            /*ArrayList<Integer> list = new ArrayList<>();
            for (String s : map.keySet()) {
                Integer i = map.get(s);
                list.add(i);
            }*/
            Collection<Integer> values = map.values();
            ArrayList<Integer> list = new ArrayList<>();
            list.addAll(values);
            return list;
        };
        //2 将学生姓名和成绩封装到map中
        Map<String,Integer> map = new HashMap<String, Integer>();
        map.put("岑小村", 59);
        map.put("谷天洛", 82);
        map.put("渣渣辉", 98);
        map.put("蓝小月", 65);
        map.put("皮几万", 70);

        //利用Function求平均成绩
        Integer avg = f2.andThen(f1).apply(map);
        System.out.println("学生平均成绩为:"+avg);
    }

练习:获取流

简述单列集合、双列集合、数组分别如何获取Stream流对象,并进行演示

1.java.util.Collection接口中加入了default方法 stream()获取流对象,因此其所有实现类均可通过此方式获取流

2.java.util.Map接口想要获取流,先通过KeySet()、values()或entrySet()方法获取键、值或键值对的单列集合,再通过stream()获取流对象

3.数组获取流,使用Stream接口中的静态方法of(T...values)获取流
public static void main(String[] args) {
  List<String> list = new ArrayList<>();
  Stream<String> stream1 = list.stream();
  Set<String> set = new HashSet<>();
  Map<String, String> map = new HashMap<>();

  Stream<String> stream2 = set.stream();
  Stream<String> keyStream = map.keySet().stream();
  Stream<String> valueStream = map.values().stream();
  Stream<Map.Entry<String,String>>entryStream = map.entrySet().stream();

  String[] array = {"东邪", "西毒", "南帝", "北丐", "中神通"};
  Stream<String> stream = Stream.of(array);
}

练习:过滤[filter]、结果收集(数组)

有如下7个元素黄药师,冯蘅,郭靖,黄蓉,郭芙,郭襄,郭破虏,使用Stream将以郭字开头的元素存入新数组

public class Test {
    public static void main(String[] args) {
          Stream<String> stream = Stream.of("黄药师", "冯蘅", "郭靖", "黄蓉", "郭芙", "郭襄", "郭破虏");
        String[] guos = stream.filter(s -> s.startsWith("郭")).toArray(String[]::new);
     }
}
// filter 返回由与此给定谓词匹配的此流的元素组成的流。
// toArray 返回一个包含此流的元素的数组 结果: 一个包含此流的元素的数组

练习:n 取用前几个[limit]、跳过前几个[skip]

已知ArrayList集合中有如下元素{陈玄风、梅超风、陆乘风、曲灵风、武眠风、冯默风、罗玉风},使用Stream
1,取出前2个元素并在控制台打印输出。
2.取出后2个元素并在控制台打印输出。

import java.util.ArrayList;
    public class Test04 {
      public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("陈玄风");
        list.add("梅超风");
        list.add("陆乘风");
        list.add("曲灵风");
        list.add("武眠风");
        list.add("冯默风");
        list.add("罗玉风");

        list.stream().limit(2).forEach(System.out::println);
        list.stream().skip(list.size() - 2).forEach(System.out::println);
    }
}

练习:n 映射[map]、逐一消费[forEach]

有如下整数1,-2,-3,4,-5
使用Stream取元素绝对值并打印

import java.util.stream.Stream;
   public class Test {
      public static void main(String[] args) {
          Stream<Integer> stream = Stream.of(1, -2, -3, 4,-5);
        stream.map(Math::abs).forEach(System.out::println);
     }
}

练习:组合[concat]、结果收集[list]

已知数组arr1中有如下元素{郭靖,杨康},arr2中有如下元素{黄蓉,穆念慈},使用Stream将二者合并到List集合

import java.util.stream.Stream;
  public class Test {
    public static void main(String[] args) {
          Stream<String> streamA = Stream.of("郭靖", "杨康");
        Stream<String> streamB = Stream.of("黄蓉", "穆念慈");
        List<String> strList = Stream.concat(streamA, streamB).collect(Collectors.toList());
     }
}

练习:获取并发流

请分别写出获取并发流的两种方式。

public class Test {
    public static void main(String[] args) {
          Collection<String> coll = new ArrayList<>();
          Stream<String> parallelStream1 = coll.parallelStream();
        Stream<Integer> parallelStream2 = Stream.of(100, 200, 300, 400).parallel();
     }
}

函数式接口习题

练习:函数式接口

①. 定义一个函数式接口CurrentTimePrinter,其中抽象方法void printCurrentTime(),使用注解@FunctionalInterface
②. 在测试类中定义static void showLongTime(CurrentTimePrinter timePrinter),该方法的预期行为是使用timePrinter打印系统当前毫秒值
③. 测试showLongTime(),通过lambda表达式完成需求

@FunctionalInterface
public interface CurrentTimePrinter
{
    void printCurrenTime();
}

public class Test01 {
    public static void main(String[] args) {
        showLongTime(()->System.out.println(System.currentTimeMillis()));
    }

    public static void showLongTime(CurrentTimePrinter timePrinter){
        timePrinter.printCurrentTime();
    }
}

练习:函数式接口

①. 定义一个函数式接口IntCalc,其中抽象方法int calc(int a , int b),使用注解@FunctionalInterface
②. 在测试类中定义static void getProduct(int a , int b ,IntCalc calc), 该方法的预期行为是使用calc得到a和b的乘积并打印结果
③. 测试getProduct(),通过lambda表达式完成需求

IntCalc接口:

@FunctionalInterface
 public interface IntCalc {
   int calc(int a, int b);
 }

测试类:

public class Test02 {
   public static void main(String[] args) {
     getProduct(2,3,(a,b)->a*b);
   }
   public static void getProduct(int a, int b, IntCalc intCalc){
     int product = intCalc.calc(a,b);
     System.out.println(product);

   }
}

练习:静态方法引用

①. 定义一个函数式接口NumberToString,其中抽象方法String convert(int num),使用注解@FunctionalInterface
②. 在测试类中定义static void decToHex(int num ,NumberToString nts), 该方法的预期行为是使用nts将一个十进制整数转换成十六进制表示的字符串,**tips:已知该行为与Integer类中的toHexString方法一致**
③. 测试decToHex (),使用方法引用完成需求

interface NumberToString{
    String convert(int num);
}

public class Test {
    public static void main(String[] args) {
        decToHex(999, Integer::toHexString);
    }

    public static void decToHex(int num ,NumberToString nts){
        String convert = nts.convert(num);
        System.out.println(convert);
    }

}

字节流&字符流&Properties集合习题

练习:字节输出流写出字节数据

利用字节输出流一次写一个字节的方式,向D盘的a.txt文件输出字符‘a’

操作步骤:

1.创建字节输出流FileOutputStream对象并指定文件路径。
2.调用字节输出流的write(int byte)方法写出数据

public class Test01_01 {
    public static void main(String[] args) throws IOException {
        // 1.创建字节输出流FileOutputStream对象并指定文件路径。
        FileOutputStream fos = new FileOutputStream("d:/a.txt");
        // 2.调用字节输出流的write(byte[] buf)方法写出数据。
        byte[] buf = "i love java".getBytes();
        // 2.调用字节输出流的write(int byte)方法写出数据
        fos.write(97);
        // 3.关闭流
        fos.close();
    }
}

练习:文件的续写和换行输出

在D盘下,有一c.txt 文件中内容为:HelloWorld
在c.txt文件原内容基础上,添加五句 I love java,而且要实现一句一行操作(注:原文不可覆盖)。利用字节输出流对象往C盘下c.txt文件输出5句:”i love java”

操作步骤:

1.利用两个参数的构造方法创建字节输出流对象,参数一指定文件路径,参数二指定为true
2.调用字节输出流的write()方法写入数据,在每一行后面加上换行符:”\r\n”

public class Test01_03 {
    public static void main(String[] args) throws IOException{
        // 1.创建字节输出流FileOutputStream对象并指定文件路径,并追加方式
        FileOutputStream fos = new FileOutputStream("c:/c.txt",true);
        // 2.调用字节输出流的write方法写出数据
        // 2.1 要输出的字符串
        String content = "i love java \r\n";
        for (int i = 0; i< 5; i++) {
            fos.write(content.getBytes());
        }
        // 3.关闭流
        fos.close();
    }
}
/*
    定义变量接收读取的字节
        int len = -1;
        // 循环从流中读取数据
        while((len = fis.read()) != -1) {
        System.out.print(new String(buffer,0,len));
}

*/

练习:字节流复制文件

描述: 利用字节流将E盘下的a.png图片复制到D盘下(文件名保存一致)

要求:一次读写一个字节的方式

操作步骤:

1.创建字节输入流对象关联文件路径:E盘下的a.png
2.创建字节输出流对象关联文件路径:D盘下的a.png
3.使用循环不断从字节输入流读取一个字节,每读取一个字节就利用输出流写出一个字节。
4.关闭流,释放资源

public class Test01_06 {
    public static void main(String[] args) throws IOException {
        // 创建字节输入流对象并关联文件
        FileInputStream fis = new FileInputStream("e:/a.png");
        // 创建字节输出流对象并关联文件
        FileOutputStream fos = new FileOutputStream("d:/a.png");
        // 定义变量接收读取的字节数
        int len = -1;
        // 循环读取图片数据
        while((len = fis.read()) != -1) {
            // 每读取一个字节的数据就写出到目标文件中
            fos.write(len);
        }
        // 关闭流
        fis.close();
        fos.close();
    }

练习:IO对象Properties结合使用,设置properties文件

我有一个文本文件score.txt,我知道数据是键值对形式的,但是不知道内容是什么。
请写一个程序判断是否有”lisi”这样的键存在,如果有就改变其实为”100”
score.txt文件内容如下:
zhangsan = 90 lisi = 80 wangwu = 85
操作步骤:
1.创建一个空的Properties集合
2.读取数据到集合中
3.遍历集合,获取到每一个key
4.判断当前的key 是否为 “lisi”,如果是就把”lisi”的值设置为100
5.把集合中所有的信息,重新存储到文件中

void store(OutputStream out, String comments)
此适合使用load(InputStream)方法加载到Properties表中的格式,将此Propeirties表中的属性列表(键和元素对写入输出流)
public class Test02_06 {
    public static void main(String[] args) throws IOException {
//1:创建一个空的集合
        Properties prop = new Properties();
//2:读取数据到集合中
        prop.load(new FileInputStream("score.txt"));
//3:遍历集合,获取到每一个key
        Set<String> keys = prop.stringPropertyNames();
//获取到每一个key
        for (String key : keys) {
//4:判断当前的key 是否为 "lisi"
            if ("lisi".equals(key)) {
//把"lisi"的值设置为100
                prop.setProperty(key, "100");
            }
        }
//把集合中所有的信息,重新存储到文件中
        prop.store(new FileOutputStream("score.txt"), "haha");
    }
}

缓冲流&转换流习题

练习:高效字节输出流写出字节数据

描述: 利用高效字节输出流往C盘下的d.txt文件输出一个字节数。

操作步骤:

1.创建字节输出流对象关联文件路径
2.利用字节输出流对象创建高效字节输出流对象
3.调用高效字节输出流对象的write方法写出一个字节
4.关闭高效流,释放资源。

public class Test01_01 {
    public static void main(String[] args) throws IOException {
        // 创建字节输出流FileOutputStream对象并指定文件路径。
        FileOutputStream fos = new FileOutputStream("c:\\d.txt");
        // 利用字节输出流创建高效字节输出流对象
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        // 调用高效字节输出流对象的write(int byte)方法写出一个字节数据
        bos.write(97);
        // 关闭流
        bos.close();
    }
}

练习:高效字节输出流写出字节数组数据

描述: 利用高效字节输出流往C盘下的e.txt文件写出一个字节数组数据,如写出:”i love java”

操作步骤:

1.创建字节输出流对象关联文件路径
2.利用字节输出流对象创建高效字节输出流对象
3.定义字符串存放要输出的数据,然后将字符串转换为字节数组。
4.调用高效字节输出流对象的write方法将字节数组输出。
5.关闭高效流。

public class Test01_02 {
    public static void main(String[] args) throws IOException {
        // 创建字节输出流FileOutputStream对象并指定文件路径。
        FileOutputStream fos = new FileOutputStream("c:\\e.txt");
        // 利用字节输出流创建高效字节输出流对象
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        // 调用高效字节输出流对象的write(byte[] buff)方法写出一个字节数据
        bos.write("i love java".getBytes());
        // 关闭流
        bos.close();
    }
}

练习:高效流文件复制

描述: 利用高效字节输入流和高效字节输出流完成文件的复制。

要求:

1.将C盘下的c.png文件复制到D盘下
2.一次读写一个字节数组方式复制

操作步骤:

①.创建字节输入流对象并关联文件路径
②.利用字节输入流对象创建高效字节输入流对象
③.创建字节输出流对象并关联文件路径
④.利用字节输出流对象创建高效字节输出流对象
⑤.创建字节数组用来存放读取的字节数
⑥.利用高效字节输入流循环读取文件数据,每读取一个字节数组,利用高效字节输出流对象将字节数组的内容输出到目标文件中。直到读取到文件末尾。
⑦.关闭高效流对象

public class Test01_03 {
    public static void main(String[] args) throws IOException{
        // 创建字节输入流对象并关联文件路径
        FileInputStream fis = new FileInputStream("c:\\c.png");
        // 利用字节输出流对象创建高效字节输出流对象
        BufferedInputStream bis = new BufferedInputStream(fis);
        // 创建字节输出流对象并指定文件路径。
        FileOutputStream fos = new FileOutputStream("d:\\c.png");
        // 利用字节输出流创建高效字节输出流对象
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        // 定义字节数组接收读取的字节
        byte[] buffer = new byte[1024];
        // 定义变量接收读取的字节数
        int len = -1;
        // 循环读取图片数据
        while((len = bis.read(buffer)) != -1) {
            // 每读取一个字节的数据就写出到目标文件中
            bos.write(buffer,0,len);
        }
        // 关闭流
        bis.close();
        bos.close();
    }
}

练习:高效字符流和集合的综合使用

描述:

分析以下需求,并用代码实现
实现一个验证码小程序,要求如下:
① 在项目根目录下新建一个文件:data.txt,键盘录入3个字符串验证码,并存入data.txt中,要求一个验证码占一行;
② 键盘录入一个需要被校验的验证码,如果输入的验证码在data.txt中存在:在控制台提示验证成功,如果不存在控制台提示验证失败

public class Test {
    public static void main(String[] args) throws IOException {
        writeString2File();
        verifyCode();
    }

    private static void writeString2File() throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter(new File("D:\\Clash\\a.txt")));
        String line = null;
        Scanner sc = new Scanner(System.in);
        for (int i = 0; i < 3; i++) {
            System.out.println("请输入第"+(i+1)+"个字符串验证码");
            line = sc.nextLine();
            bw.write(line);
            bw.newLine();
        }
        bw.close();
    }

    private static void verifyCode() throws IOException {
        ArrayList<String> list = new ArrayList<>();
        BufferedReader br = new BufferedReader(new FileReader(new File("D:\\Clash\\a.txt")));
        String line = null;
        while(null!=(line = br.readLine())){
        list.add(line);
    }
        br.close();
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个验证码");
        String code = sc.nextLine();
        if (list.contains(code)){
            System.out.println("验证成功");
        }else {
            System.out.println("验证失败");
        }
    }
}

练习:转换输出流的使用

描述: 现有一字符串:”我爱Java”。将该字符串保存到当前项目根目录下的a.txt文件中。

要求:使用gbk编码保存。
注意:idea的默认编码是utf-8,所以可以通过fileàsettingsàfile encodings设置为gbk格式,否则打开a.txt文件看到的将会是乱码。

操作步骤:

1.创建文件字节输出流关联目标文件
2.根据文件字节输出流创建转换输出流对象,并指定编码字符集为:gbk
3.调用流对象的方法将字符串写出到文件中。
4.关闭流并释放资源。

public class Test01_05 {
    public static void main(String[] args) throws IOException{
        // 要保存的字符串
        String content = "我爱Java";
        // 创建字节输出流对象
        FileOutputStream fos = new FileOutputStream("a.txt");
        // 创建转换输出流对象
        OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
        // 调用方法写出数据
        osw.write(content);
        // 关闭流释放资源
        osw.close();
    }
}

练习:转换输入流的使用

描述: 利用转换输入流将当前项目根目录下使用gbk编码的a.txt文件的内容读取出来,并打印在控制台上。

要求:不能出现乱码的情况。
操作步骤:

1.创建字节输入流对象指定文件路径。
2.根据字节输入流对象创建转换输入流对象并指定字符集编码为:gbk
3.调用转换输入流对象的读取方法读取内容
4.关闭流释放资源

public class Test01_06 {
    public static void main(String[] args) throws IOException{
        // 创建字节输入流对象并关联文件
        FileInputStream fis = new FileInputStream("a.txt");
        // 创建转换输入流对象
        InputStreamReader isr = new InputStreamReader(fis,"gbk");
        // 定义字符数组存放读取的内容
        char[] buffer = newchar[1024];
        // 定义变量接收读取的字符个数
        intlen = -1;
        while((len = isr.read(buffer)) != -1) {
            System.out.print(new String(buffer,0,len));
        }
        // 关闭流
        isr.close();
    }
}

继承&抽象类习题

概念

什么叫做类与类的继承,作用是什么?

就是子类继承父类的属性和行为,使得子类对象具有与父亲相同的属性、相同的行为;
作用是子类复用父类的内容

继承后,父类与子类之间,各成员有什么样的影响?

成员变量:
不重名,就没有影响;重名,就近使用,使用super分区父类变量

构造方法:
无影响,但是子类构造方法默认调用父类构造方法

成员方法:
不重名,没有影响;重名,子类重写父类方法

子类中,如何调用父类的成员?如何使用本类的成员?

父类成员方法:super.方法名
父类非私有成员变量:super.变量名
子类成员方法:this.方法名
子类成员变量:this.变量名

抽象方法与普通成员方法有什么区别?

抽象方法使用abstract关键字修饰,没有方法体;成员方法有方法体

抽象类与普通类有什么区别?

方法:
抽象类可以包含抽象方法和成员方法;普通类不可以包含抽象方法,只有成员方法

对象:
抽象类不可以创建对象;普通类可以创建对象

练习:语法练习

  • 语法点:继承,抽象类

  • 输出A类中numa:10、B类中numb:20、C类中numc:30

abstract  class A{
    int numa = 10;
    public abstract void showA();
}

abstract class B extends A{
    int numb = 20;
    public abstract void showB();
}

class C extends B{
    int numc = 30;

    @Override
    public void showA() {
        System.out.println("A类中numa:"+numa);
    }

    @Override
    public void showB() {
        System.out.println("B类中numb:"+numb);

    }
    public void showC(){
        System.out.println("C类中numc:"+numc);
    }
}
public class Test {
    public static void main(String[] args) {
        C c = new C();
        c.showA();
        c.showB();
        c.showC();
    }
}

练习:语法练习

  • 语法点:继承,抽象类
  • 输出动物种类:鸭子,年龄:2岁、 入院原因:感冒、 症状为:发烧

编写步骤:

  1. 模拟农学院动物医疗系统信息。
  2. 定义抽象家禽类(Poultry)
    1. 私有成员变量:动物种类(name),症状(symptom),年龄(age), 病因(illness)
    2. 提供空参和带参构造方法
    3. 成员方法:
      1. 抽象方法症状(showSymptom)
      2. 普通方法基本信息(showMsg)
      3. 提供setXxx和getXxx方法
  3. 定义普通鸭子类(Duck)
    1. 提供空参和带参构造方法
    2. 重写showSymptom方法,打印症状信息。
public class Test3 {
    public static void main(String[] args) {
        Duck duck = new Duck("鸭子", "感冒", "发烧", 2);
        duck.showMsg();
        duck.showSymptom();
    }
}

/*
1.定义抽象家禽类(Poultry)
*/
abstract class Poultry {
    //    i.成员变量(私有):

    private String name;
    private String illness;

    // 症状(symptom)
    private String symptom;
    //    年龄(age)
    private int age;

    //    ii.成员方法:  showSymptom
    public abstract void showSymptom();

    // 成员方法:  showMsg
    public void showMsg() {
        System.out.print("动物种类:" + name);
        System.out.println(",年龄:" + age + "岁");
        System.out.println("入院原因:" + illness);
    }

    //    iii.提供空参和带参构造方法
    public Poultry() {
        super();
    }

    public Poultry(String name, String illness, String symptom, int age) {
        this.name = name;
        this.illness = illness;
        this.symptom = symptom;
        this.age = age;
    }

    //    iv.提供setXxx和getXxx方法
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getIllness() {
        return illness;
    }

    public void setIllness(String illness) {
        this.illness = illness;
    }

    public String getSymptom() {
        return symptom;
    }

    public void setSymptom(String symptom) {
        this.symptom = symptom;
    }
}

// Duck 类

class Duck extends Poultry {

    public Duck() {

    }

    public Duck(String name, String illness, String symptom, int age) {

        super(name, illness, symptom, age);

    }

    @Override

    public void showSymptom() {

        System.out.println("症状为:" + getSymptom());

    }

}

练习:语法练习

  • 语法点:继承

  • 输出:王小平老师,讲授Java课、 李小乐同学,考试得了90分

编写步骤:

  1. 模拟教学管理系统师生信息。
  2. 定义Person类。
    1. 属性:姓名、年龄
    2. 构造方法:无参构造方法,有参构造方法
    3. 成员方法:getXxx方法,setXxx方法,显示基本信息showMsg方法
  3. 定义Teacher类,继承Person
    1. 属性:学科
    2. 构造方法:无参构造方法,有参构造方法
    3. 成员方法:getXxx方法,setXxx方法,讲课方法
  4. 定义Student类,继承Person
    1. 属性:分数
    2. 构造方法:无参构造方法,有参构造方法
    3. 成员方法:getXxx方法,setXxx方法,考试方法
public class Test {

    public static void main(String[] args) {
        //        i.创建老师对象t,并把名称赋值为”王小平”,年龄赋值为30,工资赋值为8000
        Teacher t = new Teacher("王小平", 30, "Java");
        //        iii.调用老师对象t的讲解方法
        t.teach();

        //        iv.创建学生对象 s,并把名称赋值为”李小乐”,年龄赋值为14,成绩赋值为90分.
        Student s = new Student("李小乐", 14, 90);
        //        vi.调用学生对象 s 的考试方法
        s.exam();
    }
}

class Person {
    // 名称(name)
    private String name;
    //    年龄(age)
    private int age;

    //    空参构造
    public Person() {
    }
    //  带参构造
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // setXxx和getXxx方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
/*
 2.定义老师类(Teacher),继承Person类
 */
class Teacher extends Person {
    //    course(科目)
    private String course;
    //    空参构造
    public Teacher() {
    }
    //    带参构造方法
    public Teacher(String name,int age, String course) {
        super(name,age);
        this.course = course;
    }

    //    提供setXxx和getXxx方法
    public String getCourse() {
        return course;
    }
    public void setCourse(String course) {
        this.course = course;
    }

    public void teach() {
        System.out.println(getName() +"老师,讲授"+course +"课");
    }
}
/*
 3.定义学生类(Student),继承Person类
 */
class Student extends Person {
    //    score(成绩)
    private int score;
    //    无参构造
    public Student() {
        super();
    }
    //    带参构造
    public Student(String name, int age,int score) {
        super(name, age);
        this.score = score;
    }

    //    提供setXxx和getXxx方法
    public int getScore() {
        return score;
    }
    public void setScore(int score) {
        this.score = score;
    }

    public void exam(){
        System.out.println(getName()+"同学,考试得了:"+ score +"分");
    }
}

练习:语法练习

  • 语法点:继承

  • 编写步骤

    1. 模拟汽车网站信息。
    2. 定义汽车Auto类
      1. 属性:品牌,车长,价格
    3. 定义SUV继承Auto类
      1. 属性:小型车车长标准值:4295,中型车车长标准值:5070。
      2. 定义判断车型方法
        1. 判断小型车:小于小型车车长标准值
        2. 判断大型车:大于中型车车长标准值
        3. 判断中型车:大于小型车车长标准值并且小于等于中型车车长标准值
    4. 测试类中,创建若干SUV对象,保存到集合,遍历集合,输出中型SUV。
public class Test5 {
    public static void main(String[] args) {
        // 创建SUV对象
        SUV suv1 = new SUV(5079, 750000);
        SUV suv2 = new SUV(4813, 760000);
        SUV suv3 = new SUV(4270, 127800);
        SUV suv4 = new SUV(4545, 188800);

        //添加到集合中
        ArrayList<SUV> list = new ArrayList<>();
        list.add(suv1);
        list.add(suv2);
        list.add(suv3);
        list.add(suv4);

        // 遍历集合,查询中型SUV
        for (int i = 0; i < list.size(); i++) {
            SUV suv = list.get(i);
            if (suv.midSUV()){
                suv.showMsg();
            }
        }
    }
}
// 定义汽车类
class Auto {
    private String type;
    private double length;
    private double price;

    public Auto() {
    }

    public Auto(String type, double length, double price) {
        this.type = type;
        this.length = length;
        this.price = price;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public void showMsg() {
        System.out.println("车型:" + type);
        System.out.println("\t价格:" + price);
        System.out.println("\t车长:" + length);

    }

}

// 定义SUV类
class SUV extends Auto {
    // 车长标准
    private int miniLength = 4295;
    private int midLength = 5070;

    public SUV(double length, double price) {
        super("SUV", length, price);
    }
    // 判断 小型车
    public boolean miniSUV() {
        return getLength() <= miniLength;
    }

    // 判断 大型车
    public boolean largeSUV() {
        return getLength() > midLength;
    }

    // 判断 中型车
    public boolean midSUV() {
        return getLength() > miniLength && getLength() <= midLength;
    }
}

接口&多态

概念辨析

什么是接口,如何定义接口?
  • 接口,是java语言中一种类型,是方法的集合
  • 使用interface关键字定义接口,其中可以定义抽象方法,默认方法,私有方法,静态方法等方法
什么叫做多态,条件是什么?

一类事物的行为,具有多种表现形式
条件:

  • 继承或实现[二选一]
  • 方法的重新
  • 父类引用指向子类对象
使用多态特性,带来了什么样的好处?

增强方法的扩展性和复用性

使用多态特性,注意什么样的弊端?

由于类型的提升,导致调用子类对象特有的方法,必须向下转型。

练习:接口

输出AAAA\n BBBB

编写步骤:

  1. 定义接口A,普通类B实现接口A
  2. A接口中,定义抽象方法showA。
  3. A接口中,定义默认方法showB。
  4. B类中,重写showA方法
  5. 测试类中,创建B类对象,调用showA方法,showB方法。
interface A{
    public abstract void showA(); //抽象方法
    public default void showB(){ //默认方法
        System.out.println("BBB");
    }
}

class B implements A{
    @Override
    public void showA() {
        System.out.println("AAAA");
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
        b.showA();
        b.showB();

    }
}

练习:接口

输出AAA\n BBBB BBBB BBBB BBBB\n CCCC CCCC CCCC CCCC

编写步骤:

  1. 定义接口A,普通类B实现接口A。
  2. A接口中,定义抽象方法showA。
  3. A接口中,定义私有方法show10(String str),循环打印10次str。
  4. A接口中,定义默认方法showB10,showC10,分别调用show10方法,传入参数。
  5. 测试类中,创建B对象,调用showA方法,showB10方法,showC10方法
interface AA{
    public abstract void showA();
    private void show10(String str){
        for (int i = 0; i < 10; i++) {
            System.out.print(str + " ");
        }
        System.out.println();
    }
    public default void show10B(){
        show10("BBBB");
    }
    public default void show10C(){
        show10("CCCC");
    }
}
class BB implements AA{
    @Override
    public void showA() {
        System.out.println("AAAA");
    }
}

public class Test {
    public static void main(String[] args) {
        BB b = new BB();
        b.showA();
        b.show10B();
        b.show10C();
    }
}

练习:接口,静态

  • 编写步骤
  1. 定义接口A,普通类B实现接口A。
  2. A接口中,定义抽象方法showA。
  3. A接口中,定义私有静态方法show10(String str),循环打印10次str。
  4. A接口中,定义静态方法showB(),showC(),分别调用show10方法,传入参数。
  5. B类中,定义静态方法showD
  6. 测试类中,使用A接口,调用静态showB()方法,showC()方法,
  7. 测试类中,使用B类,调用showA方法,showD方法。
interface AAA{
    public abstract void showA();

    public static void showB() {
        System.out.println("static BBBB");
        show10("BBBB");
    }
    public static void showC(){
        System.out.println("static CCCC");
        show10("CCCC");
    }
    private static void show10(String str){
        for (int i = 0; i < 10; i++) {
            System.out.println(str + " ");
        }
        System.out.println();
    }
}

class BBB implements AAA{

    @Override
    public void showA() {
        System.out.println("AAA");
    }
    public void showD(){
        System.out.println("DDDD");
    }
}

public class Test {
    public static void main(String[] args) {
        AAA.showB();
        AAA.showC();
        BBB bbb = new BBB();
        bbb.showA();
        bbb.showD();
    }
}

练习:接口,多态

输出:star:星星一闪一闪亮晶晶\n =======\n sun:太阳引着9大行星旋转\n sun:光照八分钟,到达地球

编写步骤

  1. 定义接口Universe,提供抽象方法doAnything。
  2. 定义普通类Star,提供成员发光shine方法
  3. 定义普通类Sun,继承Star类,实现Universe接口
  4. 测试类中,创建Star对象,调用shine方法
  5. 测试类中,多态的方式创建Sun对象,调用doAnything方法,向下转型,调用shine方法。
interface Universe{
    public abstract void doAnything();
}
class Star{
    public void shine(){
        System.out.println("star:星星一闪一闪亮晶晶");
    }
}
class Sun extends Star implements  Universe{
    @Override
    public void doAnything() {
        System.out.println("sun:太阳吸引着9大行星旋转");
    }
    @Override
    public void shine() {
        System.out.println("sun:光照八分钟,到达地球");
    }
}

public class Test {
    public static void main(String[] args) {
        Star s = new Star();
        s.shine();
        System.out.println("====================");
        Universe universe = new Sun();
        universe.doAnything();
        Sun sun = (Sun)universe;
        sun.shine();
    }
}

内部类

练习:需求实现

  • 定义HandleAble接口,具备一个处理字符串数字的抽象方法方法HandleString(String num)。

    • 处理方式1:取整数部分。
    • 处理方式2:保留指定位小数,四舍五入。
  • 开发提示:

    • 匿名内部类[接口不能带方法体可以匿名内部类]的方式,调用所有抽象方法
interface HandleAble{
    String handleString(String str);
}

public class Test {
    public static void main(String[] args) {
        String str = "23.23456789";
        System.out.println("原字符串是:" + str);
        HandleAble s1 = new HandleAble() {
            @Override
            public String handleString(String str) {
                return str.substring(0,str.indexOf("."));
            }
        };
        System.out.println("取整后:" + s1.handleString(str));

        int num = 4;

        HandleAble s2 = new HandleAble() {
            @Override
            public String handleString(String str) {

                int i = str.indexOf(".") + num + 1;
                char c = str.charAt(i);
                //System.out.println(c);

                if (c <= '4') {
                    return str.substring(0, i).toString();
                } else {
                    char c1 = (char) (str.charAt(str.indexOf(".") + num) + 1);
                    return str.substring(0, i - 1) + c1;

                }
            }
        };
        String sss = s2.handleString(str);
        System.out.println("保留" + num + "位小数后:" + sss);
    }
}

练习:需求实现

  • 模拟上课出勤情况。
  • 定义学生类:

    • 属性:姓名,出勤。
    • 提供基本的构造方法和get方法,set方法。
  • 定义讲师类:

    • 属性:姓名。
    • 提供基本的构造方法和get方法,set方法
    • 成员方法:点名方法,设置每一位的学生出勤情况。假设,小明今日未出勤。
  • 定义课程类:

    • 属性:课程名称,讲师,学生集合。
    • 提供基本的构造方法和get方法,set方法
    • 成员方法:show方法,打印课程信息,老师姓名,学生是否上课情况。

课程名称:Java
授课老师:张老师
上课:小红
上课:小亮
旷课:小明

public class Test {
    public static void main(String[] args) {
        Student s = new Student("小红");
        Student s1 = new Student("小亮");
        Student s2 = new Student("小明");
        ArrayList<Student> arr = new ArrayList<>();
        arr.add(s);
        arr.add(s1);
        arr.add(s2);
        Teacher t = new Teacher("张老师");
        Course course = new Course("java",t,arr);
        t.dianming(arr);
        course.show();

    }
}

class Student{
    private String name;
    private boolean come;

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isCome() {
        return come;
    }

    public void setCome(boolean come) {
        this.come = come;
    }
}
class Teacher{
    private String name;
    // 点名方法,设置每一位的学生出勤情况。假设,小明今日未出勤
    public void dianming(ArrayList<Student> arr){
        for (int i = 0; i < arr.size(); i++) {
            Student student = arr.get(i);
            if (!student.getName().equals("小明")){
                student.setCome(true);
            }
        }
    }
    public Teacher() {
    }

    public Teacher(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
class Course{
    private String name;
    private Teacher t;
    private ArrayList<Student> arr;

    public Course() {
    }

    public Course(String name, Teacher t, ArrayList<Student> arr) {
        this.name = name;
        this.t = t;
        this.arr = arr;
    }
    public void show(){
        System.out.println("课程名称:" + name);
        System.out.println("授课老师:" + t.getName());
        for (int i = 0; i < arr.size(); i++) {
            Student student = arr.get(i);
            String name = student.getName();
            if (student.isCome()) {
                System.out.println("上课: " + name);
            } else {
                System.out.println("旷课: " + name);
            }
        }
    }
}

练习:需求实现

  • 模拟接待员接待用户,根据用户id,给用户分组。
  • 定义接口Filter:
    • 提供抽象方法filterUser(User u)
  • 定义用户类:
    • 属性:用户类型,用户id
    • 提供基本的构造方法和get方法,set方法
  • 定义接待员类:
    • 属性:接口Filter
    • 提供基本的构造方法和get方法,set方法
    • 成员方法:接待用户方法,设置用户类型。
  • 测试类:
    • 初始化50个User对象,id为1-50。
    • 创建三个接待员对象。
      • 第一个接待员,设置接待规则,将10-19号用户类型设置为v1。
      • 第二个接待员,设置接待规则,将20-29号用户类型设置为v2。
    • 遍历用户集合,给用户分区。
public class Test {
    public static void main(String[] args) {

        ArrayList<User> ulist = new ArrayList<>();
        for (int i = 1; i <= 50; i++) {
            ulist.add(new User(i));
        }

        System.out.println("未分组:");
        System.out.println(ulist);

        Reception r1 = new Reception();
        Reception r2 = new Reception();
        Reception r3 = new Reception();
        r1.setF(new Filter() {
            @Override
            public void filterUser(User u) {
                if (u.getId() >= 10 && u.getId() < 20)
                    u.setType("v1");
            }
        });

        r2.setF(new Filter() {
            @Override
            public void filterUser(User u) {
                if (u.getId() >= 20 && u.getId() < 30)
                    u.setType("v2");
            }
        });

        for (int i = 0; i < ulist.size(); i++) {
            User user = ulist.get(i);
            r1.recept(user);
            r2.recept(user);
            r3.recept(user);
        }
        System.out.println("已分组:");
        for (int i = 0; i < ulist.size(); i++) {
            User user = ulist.get(i);
            if (i % 9 == 0) {
                System.out.println();
            }
            System.out.print(user + " ");
        }

    }
}

class Reception {

    Filter f;

    public Filter getF() {
        return f;
    }

    public void setF(Filter f) {
        this.f = f;
    }

    public void recept(User u) {
        if (u.getType() != null)
            return;
        if (f != null) {
            f.filterUser(u);
            return;
        } else {
            u.setType("A");
        }
    }
}

class User {

    private String type;

    private int id;

    public User(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return id + "-" + type;
    }
}

interface Filter {
    public abstract void filterUser(User u);
}

练习:需求实现

  • 模拟工人挑苹果。

  • 定义苹果类:

    • 属性:大小,颜色。
    • 提供基本的构造方法和get方法,set方法
  • 定义接口CompareAble:

    • 定义默认方法compare,挑选较大苹果。
  • 定义接口实现类Compare。

  • 定义工人类:

    • 成员方法:挑选苹果Apple pickApple(CompareAble,Apple a1,Apple a2)。
  • 测试类:

    • 创建Worker对象。
    • 创建两个Apple对象,一个Apple(5,”青色”),一个Apple(3,”红色”)
    • 默认挑选大的苹果,打印苹果信息。
    • 指定颜色挑选,通过匿名内部类实现。
  • 代码实现,效果所示:

默认挑大的:
5.0 - 青色
挑红的:
3.0 - 红色

interface CompareAble{
    default Apple compare(Apple a1, Apple a2){
        return a1.getSize() > a2.getSize() ? a1 : a2;
    }
}

class Apple{
    private double size;
    private String color;

    public Apple() {
    }

    public Apple(double size, String color) {
        this.size = size;
        this.color = color;
    }

    public double getSize() {
        return size;
    }

    public void setSize(double size) {
        this.size = size;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "size=" + size +
                ", color='" + color + '\'' +
                '}';
    }
}

class Worker{
    public Apple pickApple(CompareAble c, Apple a1, Apple a2){
        Apple compare = c.compare(a1,a2);
        return compare;
    }


public class Test {
    public static void main(String[] args) {
        Worker worker = new Worker();
        Apple apple1 = new Apple(5, "青色");
        Apple apple2 = new Apple(3, "红色");
        System.out.println("默认挑大的:");
        Apple apple = worker.pickApple(new Com(), apple1, apple2);
        System.out.println(apple);

        System.out.println("挑红的:");
        Apple apple3 = worker.pickApple(new Com(){
            @Override
            public Apple compare(Apple a1, Apple a2) {
                return "红色".equals(a1.getColor()) ? a1 : a2;
            }
        },apple1,apple2);
        System.out.println(apple3);
    }
    }
}

class Com implements CompareAble {

}

数组习题

练习:需求实现

模拟在一副牌中,抽取第1张,第5张,第50张扑克牌。

输出:黑桃A 黑桃5 方片J

public class Test {
    public static void main(String[] args) {
        String[] poker = getPoker();
// 抽取指定的三种扑克牌
        int num1 = 1;
        int num2 = 5;
        int num3 = 50;
        String[] pk3 = get3(poker , num1 ,num2,num3);
        // 打印抽取的牌
        for (int i = 0; i < pk3.length; i++) {
            System.out.print(pk3[i] + " ");
        }
    }
    private static String[] get3(String[] poker, int i, int i2, int i3){
        String[] pk3 = new String[3];
        pk3[0] = poker[i - 1];
        pk3[1] = poker[i2 - 1];
        pk3[2] = poker[i3 - 1];
        return pk3;
    }
    private static String[] getPoker(){
        String[] colors={"黑色","红桃","梅花","方块"};
        String[] nums={"A","2","3","4","5","6","7","8","9","10","J","Q","K"};
        String[] poker = new String[54];
        int index = 0;
        for (int i = 0; i < colors.length; i++) {
            for (int j = 0; j < nums.length; j++) {
                poker[index] = colors[i] + nums[j];
                index++;
            }
        }
        poker[52] = "小王";
        poker[53] = "大王";

        return poker;
    }
}

练习:需求实现

定义equals方法,比较数组内容是否完全一致。

开发提示:

  • 长度一致,内容一致,定义为完全一致。
public class Test {
    public static void main(String[] args) {

        int[] arr = {1,2,3,4,3,2,1};
        int[] arr2 = {1,2,3,4,3,2,1};
        System.out.println(" 是否一致:" +equals(arr ,arr2));

    }
    //  比较数组的内容
    public static boolean equals(int[] arr1, int[] arr2) {
        // 长度不同,返回false
        if (arr1.length != arr2.length) {
            return false;
        }

        //
        for (int i = 0; i < arr1.length; i++) {
            // arr1[i] 和 arr2[i]比较
            /*
             * 所有元素都相同才相同,也就是只要有一个不同,这两个数组就是不同
             */
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }
}

异常&线程习题

练习:异常的体系

1.请描述异常的继承体系
异常继承体系:异常的根类是java.lang.Throwable。其下有两个子类:java.lang.Errorjava.util.Exception
Exception又分为编译时期异常:checked异常
与运行时期异常:runtime异常

2.请描述你对错误(Error)的理解
Error:表示不可修复的恶性的错误,只能通过修改菜吗规避错误的产生,通常是系统级别的,所以很严重。

3.描述你对异常(Expection的理解)
Exception:表示可修复的良性(相对于错误)的异常,异常产生后程序员可以并且通过代码的方式修正,使程序继续运行,是必须要处理的。

4.描述你对运行时异常(RuntimeException)的理解
运行时期异常:runtime异常。在运行时期,检查异常. 在编译时期,运行异常不会编译器检测(不报错)

练习:throw与throws的区别

1.请描述throw的使用位置,作用是什么?
throw关键字通常用在方法体中,并且抛出一个异常对象。程序在执行到throw语句时立即停止,后面语句都不执行

2.请描述throws的使用位置,作用是什么?
throws关键字通常被应用在声明方法时,用来指定可能抛出的异常。多个异常可以使用逗号隔开。当在主函数中调用该方法时,如果发生异常,就会将异常对象抛给方法调用处

练习:异常的处理方式

1.异常处理方式有几种,分别是什么
异常的处理方式有两种,分别是使用 throwstry...catch...finally

2.详细阐述每种方式对异常是如何处理的
throws用在方法的声明上后接异常类名,是把异常抛给调用者进行处助理
try…catch…finally是捕获异常,自己处理,处理完毕后面的程序可以继续运行
try代码块中是可能出现异常的代码
catch代码块,是遇到异常,对异常进行处理的代码
finally代码块无论是否发生异常,都必须执行的代码,用于释放资源

练习:常见异常,及产生原因

请列举常见异常,并说明产生原因

NullPointerException:空指针异常
当应用试图在要求使用对象的方法使用了null时,抛出该异常;譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度

ArrayIndaexOutOfBoundsException:数组索引越界异常
当对数组的索引值为负数或大于等于数组大小时抛出此异常。

ArithmeticException:算术运算异常
程序中出现了除以零这样的运算就会出这样的异常,对这种异常,大家就要好好检查一下自己程序中涉及到数学运算的地方,公式是不是有不妥了

NumberFormatException:数字格式异常
当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常

练习:并行、并发概念

请简单描述什么是并行,什么是并发?

并行:指两个或多个事件在同一时刻发生(同时发生)。
并发:指两个或多个事件在同一个时间段内发生。

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不 支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
它们最关键的点就是:是否是『同时』

练习:进程概念、线程概念、线程与进程联系

请描述什么是进程,什么是线程,进程与线程之间的关系,并举例说明

进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

一个程序运行后至少有一个进程,一个进程中可以包含多个线程, 但一个进程中至少包含一个线程。比如使用迅雷软件下载网络文件时,同时下载多个文件,就使用到了多线程下载。

练习:自定义异常类

请使用代码实现
每一个学生(Student)都有学号,姓名和分数,分数永远不能为负数
如果老师给学生赋值一个负数,抛出一个自定异常

// 1.定义异常类NoScoreException,继承RuntimeException 提供空参和有参构造方法
public class NoScoreException extends RuntimeException {
    //  空参构造
    public NoScoreException() {
            super();
    }
    // 有参构造
    public NoScoreException(String message) {
            super(message);
     }
/* 2.定义学生类(Student)
   a)属性:name,score
   b)提供空参构造
   c)提供有参构造;
    i.使用setXxx方法给名称和score赋值
   d)提供setter和getter方法
    ii.在setScore(int score)方法中
    1.首先判断,如果score为负数,就抛出NoScoreException,异常信息为:分数不能为负数:xxx.
    2.然后在给成员score赋值.*/
public class Student {
    private String name;
    private int score;
    // 空参构造
    public Student() {
        super();
    }
    // c)提供有参构造;
// i.使用setXxx方法给名称和score赋值
    public Student(String name,int score){
        setName(name);
        setScore(score);
    }
// d)提供setter和getter方法

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }
    // i.在setScore(int score)方法中
    public void setScore(int score) {
// 1.首先判断,如果score为负数,就抛出NoScoreException,异常信息为:分数不能为负数:xxx.
    if(score <0){
       throw new NoScoreException(":分数不能为负数:"+score);
    }
// 2.然后在给成员score赋值.
        this.score = score;
    }
}
    /*
3.定义测试类Test9
 a)提供main方法,在main方法中
  i.使用满参构造方法创建Student对象,分数传入一个负数,运行程序
  ii.由于一旦遇到异常,后面的代码的将不在执行,所以需要注释掉上面的代码
  iii.使用空参构造创建Student对象
  iv.调用setScore(int score)方法,传入一个正数,运行程序
  v.调用setScore(int score)方法,传入一个负数,运行程序
 */
public class Test9 {
    public static void main(String[] args) {
//  i.使用满参构造方法创建Student对象,分数传入一个负数,运行程序
//  Student s = new Student("景甜", -10);
//  ii.由于一旦遇到异常,后面的代码的将不在执行,所以需要注释掉上面的代码

//  iii.使用空参构造创建Student对象
        Student s = new Student();
//  iv.调用setScore(int score)方法,传入一个正数,运行程序
        s.setScore(100);
//  v.调用setScore(int score)方法,传入一个负数,运行程序
        s.setScore(-5);
    }
}

网络通信概述&TCP协议习题

练习:ip地址和端口号概念

描述:
一、请写出IP地址的概念:
IP地址:互联网协议地址(Internet Protocol Address), 俗称IP.IP地址用来给一个网络中的计算机设备做唯一的编号.

二、请写出端口号的概念:
端口号: 端口号用来给计算机里的应用程序(进程)做唯一的标识,用2个字节表示的整数,取值范围0~65535.

练习:UDP协议

判断下列说法是否正确:( X )

由于UDP面向无连接的协议,可以保证数据完整性,因此在传输重要数据时采用UDP协议.
判断错误, 因为面向无连接,容易丢失包,所以不能保证数据完整.

练习:TCP协议

TCP协议中”三次握手”,第一次握手指的是什么:

第一次握手:客户端向服务器发送请求,等待服务器确认

练习:TCP网络协议

需求说明:创建新项目,按以下要求编写代码:

在项目下创建TCP 服务器端 端口号为8888
1: 等待客户端连接 如果有客户端连接 获取到客户端对象
2: 获取到客户端对象之后 当前在服务器读取数据客户端传送数据

public class TCPServer {
   public static void main(String[] args) throws Exception {
      //1创建服务器对象 
      ServerSocket  ss = new ServerSocket(8888);
      //2等待客户端连接   如果有客户端连接  获取到客户端对象 
      Socket socket = ss.accept();
      //3当前在服务器中  要读取数据  需要输入流  流由谁提供 客户端
      InputStream in = socket.getInputStream();//获取输入流
      //4:读数据
      int len;
      byte[] buffer = new byte[1024];
      while((len=in.read(buffer))!=-1){
          System.out.println(new String(buffer, 0, len));
      }
      //释放资源
      in.close();
//       ss.close();服务器一般不会关闭
   }
}
需求说明:创建新项目,按以下要求编写代码:

在项目下创建TCP 客户端
访问之前创建的服务器端,服务器端ip127.0.0.1 端口号8888
1: 客户端连接服务器,并发送 hello.服务器,我是客户端.
2: 开启上一题服务器,等待客户端连接,客户端连接并发送数据

public class TCPClient {
  public static void main(String[] args) throws Exception {
      //创建 Socket客户端对象
      Socket socket = new Socket("127.0.0.1", 8888);
      //写数据  需要输出流  谁提供 客户端
      OutputStream out = socket.getOutputStream();
      //写数据
      out.write("hello.服务器,我是客户端.".getBytes());
      //释放资源
      out.close();
      socket.close();
  }
}
阅读全文
头像
Asuna
You are the one who can always get to me even with screen between us.